digitalmars.D - Is this thread safe?
- dsimcha (32/32) Jul 11 2008 I'm very new to concurrent programming and am trying to implement some h...
- BCS (2/2) Jul 11 2008 It's new NG thread, and generally this is a friendly NG so probably it i...
- superdan (2/5) Jul 11 2008 took me a few seconds. now i agree with you: no contention!
- Jarrett Billingsley (3/5) Jul 11 2008 DAMN! You beat me to it!
- Walter Bright (6/9) Jul 11 2008 No, it is not thread safe. It's the old double checked locking problem.
- dsimcha (35/35) Jul 11 2008 From reading over these links, it makes sense to me why my previous impl...
- dsimcha (4/4) Jul 11 2008 Never mind, I see at least one reason why my last post would be incorrec...
- Walter Bright (5/9) Jul 11 2008 No. There are three choices:
- Koroskin Denis (68/75) Jul 13 2008 The following approach would be easier, I think:
- Jason House (31/73) Jul 12 2008 I think the following should work. Note the use of a temporary when
I'm very new to concurrent programming and am trying to implement some high performance math stuff in D. Could someone please tell me whether the following simple function would be considered thread safe? real logFactorial(uint n) { static real* factorial_table = ([0.0L]).ptr; static size_t length = 1; //Length is guaranteed to never decrease. //At least at an abstract level, the synchronized part is guaranteed to //only write to elements >= length. if(length > n) return factorial_table[n]; synchronized { if(capacity(factorial_table) < (n + 1) * real.sizeof) { //Is it safe to update the factorial_table pointer like this? factorial_table = cast(real*) realloc(factorial_table, (n + 1) * real.sizeof); } for(uint i = length; i<=n; i++) { factorial_table[i] = factorial_table[i-1] + log2(i); } //Set new length after all numbers are calculated. length = n; return factorial_table[n]; } } This function computes the log of the factorial of n, and caches the results. If it's possible without resorting to any low-level tricks that would make this function horribly unreadable, I'd like to avoid having the reads of factorial_table synchronized for performance reasons. I would guess that updating pointers and size_t variables (which are of the same representation as pointers, at least on the architectures I'm aware of) should be atomic, since otherwise the GC would not be able to properly scan pointers that were in the middle of being updated.
Jul 11 2008
It's new NG thread, and generally this is a friendly NG so probably it is. (I'm sorry, it was to good a straight line to pass up)
Jul 11 2008
BCS Wrote:It's new NG thread, and generally this is a friendly NG so probably it is. (I'm sorry, it was to good a straight line to pass up)took me a few seconds. now i agree with you: no contention!
Jul 11 2008
"BCS" <ao pathlink.com> wrote in message news:55391cb32f1a78cab18010ed04f8 news.digitalmars.com...It's new NG thread, and generally this is a friendly NG so probably it is. (I'm sorry, it was to good a straight line to pass up)DAMN! You beat me to it!
Jul 11 2008
dsimcha wrote:I'm very new to concurrent programming and am trying to implement some high performance math stuff in D. Could someone please tell me whether the following simple function would be considered thread safe?No, it is not thread safe. It's the old double checked locking problem. The reasons are difficult to understand, but understanding it is crucial to writing thread safe code. http://www.ddj.com/184405726 http://en.wikipedia.org/wiki/Double_checked_locking_pattern
Jul 11 2008
From reading over these links, it makes sense to me why my previous implementation would not work. However, the articles, which were written with C++, not D, in mind, recommend using the volatile keyword to solve this. In D, volatile is deprecated, even though, according to microbenchmarks I just tried, it has much less overhead than synchronized. I also understand from reading previous forum posts that it applies to statements, not variables. If my understanding of volatile is correct, my function could be made correct as follows, by simply inserting a volatile at the statement that reads factorial_table*. real logFactorial(uint n) { static real* factorial_table = ([0.0L]).ptr; static size_t length = 1; //Length is guaranteed to never decrease. //At least at an abstract level, the synchronized part is guaranteed to //only write to elements >= length. if(length > n) { //Length can only get larger, cached length is fine. volatile real result = factorial_table[n]; //Doesn't use cached factorial_table* return result; } synchronized { if(capacity(factorial_table) < (n + 1) * real.sizeof) { //Is it safe to update the factorial_table pointer like this? factorial_table = cast(real*) realloc(factorial_table, (n + 1) * real.sizeof); } for(uint i = length; i<=n; i++) { factorial_table[i] = factorial_table[i-1] + log2(i); } //Set new length after all numbers are calculated. length = n; return factorial_table[n]; } } Other than the fact that volatile is deprecated, would this be a correct fix? If so, why is volatile deprecated? If not, is it not correct?
Jul 11 2008
Never mind, I see at least one reason why my last post would be incorrect. The length variable could be set out-of-order before all of the factorials up to length are calculated. Is there any simple way to write this function so it's thread-safe without using synchronized just to access cached results?
Jul 11 2008
dsimcha wrote:Never mind, I see at least one reason why my last post would be incorrect. The length variable could be set out-of-order before all of the factorials up to length are calculated. Is there any simple way to write this function so it's thread-safe without using synchronized just to access cached results?No. There are three choices: 1) synchronize it 2) make thread-local versions 3) insert fences with the inline assembler
Jul 11 2008
On Sat, 12 Jul 2008 04:52:15 +0400, dsimcha <dsimcha yahoo.com> wrote:Never mind, I see at least one reason why my last post would be incorrect. The length variable could be set out-of-order before all of the factorials up to length are calculated. Is there any simple way to write this function so it's thread-safe without using synchronized just to access cached results?The following approach would be easier, I think: 1) Have an array that reallocates without invalidating old data, so that all the pointers and iterators remain valid after array resize took place 2) When need to access Nth element do the following: a) if size > N, return array[N] b) increase the capacity (atomically) c) set the array[N] to the calculated value d) use compareAndSwap (atomicStoreIf in Tango) to increase array.length e) return value Using my SafeGrowingArray from here - http://www.everfall.com/paste/id.php?tmjp3qzeo622 your code could be simplified to the following: // Important invariant assumption: // if the size is N, then values 0..N-1 are evaluated and correct. // // The steps are taken in the following order: // 1) increase capacity // 2) calculate and set the value // 3) increase size // // As a result, if another thread changes the array size to some K then it means that it is guarantied that values 0..K-1 are correct class FactorialTable : SafeGrowingArray!(real, ThreadSafetyPolicy.ThreadSafe) { real opIndex(size_t index) { size_t size = _size; if (size > index) { return super.opIndex(index); } reserve(index + 1); // step 1, ensure the capacity for (size_t i = size; i <= index; ++i) { real value = calcFactorialValue(i); // step 2 a, calculate the value this.opIndexAssign(index, value); // step 2 b, set the value. It is safe // because capacity is large and faction is deterministic } while (true) { if (atomicStoreIf(_size, size, index + 1)) { // step 3, the most important one break; // succeeded // if succeeded, the size is updated } // failed // if failed -> size is changed in another thread size = _size; if (size > index) { // check whether the size is large enough break; } // try again } return super.opIndex(index); } } FactorialTable factorialTable; static this() { factorialTable = new FactorialTable(); } real logFactorial(uint n) { return factorialTable[n]; } Use anything at your will. NOTE: not tested
Jul 13 2008
I think the following should work. Note the use of a temporary when rebuilding the table and the two volatile variables. real logFactorial(uint n) { static volatile real* factorial_table = ([0.0L]).ptr; static volatile size_t length = 1; //Length is guaranteed to never decrease. //At least at an abstract level, the synchronized part is guaranteed to //only write to elements >= length. if(length > n) { // Length can only get larger return factorial_table[n]; // factorial_table can only get longer } synchronized { if (n >= length){ real* new_factorial_table if(capacity(factorial_table) < (n + 1) * real.sizeof) { new factorial_table = cast(real*) realloc(factorial_table, (n + 1) * real.sizeof); } else { new_factorial_table = factorial_table; } for(uint i = length; i<=n; i++) { new_factorial_table[i] = factorial_table[i-1] + log2(i); } factorial_table = new_factorial_table; // safe, length too short length = n; // safe now that factorial_table is set } } return factorial_table[n]; } dsimcha wrote:From reading over these links, it makes sense to me why my previous implementation would not work. However, the articles, which were written with C++, not D, in mind, recommend using the volatile keyword to solve this. In D, volatile is deprecated, even though, according to microbenchmarks I just tried, it has much less overhead than synchronized. I also understand from reading previous forum posts that it applies to statements, not variables. If my understanding of volatile is correct, my function could be made correct as follows, by simply inserting a volatile at the statement that reads factorial_table*. real logFactorial(uint n) { static real* factorial_table = ([0.0L]).ptr; static size_t length = 1; //Length is guaranteed to never decrease. //At least at an abstract level, the synchronized part is guaranteed to //only write to elements >= length. if(length > n) { //Length can only get larger, cached length is fine. volatile real result = factorial_table[n]; //Doesn't use cached factorial_table* return result; } synchronized { if(capacity(factorial_table) < (n + 1) * real.sizeof) { //Is it safe to update the factorial_table pointer like this? factorial_table = cast(real*) realloc(factorial_table, (n + 1) * real.sizeof); } for(uint i = length; i<=n; i++) { factorial_table[i] = factorial_table[i-1] + log2(i); } //Set new length after all numbers are calculated. length = n; return factorial_table[n]; } } Other than the fact that volatile is deprecated, would this be a correct fix? If so, why is volatile deprecated? If not, is it not correct?
Jul 12 2008