Friday, 8 March 2013

Clarification about the ++ / thread-safety thing

G'day:
First things first, it's 11pm on Friday, I've just got back from being at the pub since 6pm and I have had half a dozen pints, so I don't vouch for the quality of this article.

I don't shrink away from over-stated "headlines" on this blog, but I don't like to misrepresent anything. I have a couple of things to clarify based on feedback I've had from the articles earlier today:
Firstly, the short-hand operators (++, -- +=, etc) in CFML are almost always going to work fine for you. Don't panic.

The key concept in the heading of my last article was "thread-safe". I said that the ++ operator (and by association the rest of the short-hand operators) are not thread-safe. I did not say they don't work. And almost all of anyone's code is not going to be affected by this. If you have code like this (below) you will be fine, and there's nothing to worry about:

for (var i=1; i <= 10; i++){
    // etc
}

Don't worry about the i++ in there. it's almost certainly in the variables scope, and when it runs - even on a busy site where there are concurrent requests for that code - it's being run in its own memory space. So the variable i in my request is a different variable i in your request. There's no overlap. It's fine.

The thread-safety conceit comes into play when there are multiple threads acting on the same memory (the same variable. Not the same code, but the exact same variables). This could crop up when using shared-scope variables (server, application, session), or when actively using threading (ie: via <cfthread>) like I was in my example. It might not have been clear because I was using a request-scoped variable, and as we all know the request scope is not a "shared scope". However I was using the request scope as a mechanism to share a variable between threads in a multi-threaded situation, so that kinda makes it be shared scope. Here's the code again:

cfthread.a=[];

request.sequence = 0;    // used to demonstrate which order the threads did the work

for (i=1; i <= 3; i++){
    thread name=i action="run" i=i {
        sleep(randRange(10,20));
        request.sequence++;
        arrayAppend(cfthread.a, "Appended by thread #i# @ sequence #request.sequence#");
        thread.data = cfthread.a;
    }
}

thread action="join" name="1,2,3";

writeDump(var=cfthread, label="cfthread");

I'm using the request scope here because it's accessible to everything in a given request, even stuff within a separate thread. So each of those threads has shared access to the single instance of request.sequence. And the point is the ++ operation is not atomic (it doesn't all happen simultaneously, as one unit; see the example in the preceding article), it means that if multiple threads are hitting that same statement (on the same variable in memory), then there will be "unexpected results".

However almost all usages of the ++ (etc) operator that you will have written will not be in this sort of situation, so there is no problem.

Bottom line: be mindful of this when using the server, application or session scope; but otherwise... well still be mindful of it, but it's probably not an issue.



The second thing is that Ben K asked if this consideration affects other operators too, and someone else said they know to shy away from myVar++, and instead use myVar+=1 instead.

I'm sorry to say that it's not just the ++ operator. The += operator is likewise impacted by this (and for exactly the same reason, because it runs exactly the same code under the hood). Here's a tweak of the earlier code, to use +=:

cfthread.a=[];

request.sequence = 0;    // used to demonstrate which order the threads did the work

for (i=1; i <= 3; i++){
    thread name=i action="run" i=i {
        sleep(randRange(10,20));
        request.sequence+=1;
        arrayAppend(cfthread.a, "Appended by thread #i# @ sequence #request.sequence#");
        thread.data = cfthread.a;
    }
}

thread action="join" name="1,2,3";

writeDump(var=cfthread, label="cfthread");

And this too exhibits the same behaviour as the ++ version of the code does. On both CF and Railo.

(Ben, you specifically asked about the &= operator. Yeah, that too).

I'll also observe that this behaviour is widely accepted (in the context of other languages which also have these operators) as a "consideration" with these operators, so it's not a "bug" strictly speaking: it's just something to be aware of.

I hope this clears things up a bit. And that after all that beer it's coherent (I've read a draft, it seems OK). Now I have pizza to eat.

Righto.

--
Adam