digitalmars.D.learn - Variable modified by different threads.
- Ritina (6/6) Dec 01 How can I implement a program where I have a global integer
- Andy Valencia (33/39) Dec 01 Here's my own shared memory across multiple threads sample
- =?UTF-8?Q?Ali_=C3=87ehreli?= (43/87) Dec 01 And here's mine which has interesting amount of differences:
- Andy Valencia (4/9) Dec 01 I notice core.atomic has an atomicLoad() / atomicStore() pair of
- Salih Dincer (57/59) Dec 01 I've slightly edited the code that the AI generates. Ali, how is
- Richard (Rikki) Andrew Cattermole (1/1) Dec 02 You don't need both atomics an mutex's, pick one.
- Salih Dincer (7/10) Dec 02 The compiler wants us to use atomicOp; If you want, take it out
- =?UTF-8?Q?Ali_=C3=87ehreli?= (12/17) Dec 02 Then use atomicOp. You don't need any mutex for this problem, especially...
- Richard (Rikki) Andrew Cattermole (8/14) Dec 02 That is because you used ``shared``.
- Nick Treleaven (6/13) Dec 03 `shared` is more accurate. Atomic ops are not the only way
- Richard (Rikki) Andrew Cattermole (4/12) Dec 03 That is already true. You don't need a type qualifier to tell you that.
- =?UTF-8?Q?Ali_=C3=87ehreli?= (5/11) Dec 03 I think you are objecting to D's thread-local by default decision but
- Richard (Rikki) Andrew Cattermole (8/27) Dec 03 You are thinking of globals, which are by default TLS yes.
- Andy Valencia (12/15) Dec 03 My experience is that aside from thread-local module globals, all
- Salih Dincer (74/80) Dec 01 When we ask claude.ai what is to be done, we get a very clear and
How can I implement a program where I have a global integer variable g, and three threads: the first thread increments g by 1, the second thread increments g by 2, and the third thread increments g by 3? Additionally, while these threads are running, I should be able to access the value of g both inside and outside the threads.
Dec 01
On Monday, 2 December 2024 at 02:02:56 UTC, Ritina wrote:How can I implement a program where I have a global integer variable g, and three threads: the first thread increments g by 1, the second thread increments g by 2, and the third thread increments g by 3? Additionally, while these threads are running, I should be able to access the value of g both inside and outside the threads.Here's my own shared memory across multiple threads sample program, It does a fine job of thrashing that cache line! ``` import core.atomic : atomicFetchAdd; import std.concurrency : spawn; import core.time : msecs; import core.thread : Thread; import core.memory : GC; const uint NSWEPT = 100_000_000; const uint NCPU = 4; void doadd(shared uint *buf) { for (uint count = 0; count < NSWEPT/NCPU; ++count) { atomicFetchAdd(buf[0], 1); } } void main() { shared uint *buf = cast(shared uint *)GC.calloc(uint.sizeof * 1, GC.BlkAttr.NO_SCAN); for (uint x = 0; x < NCPU-1; ++x) { spawn(&doadd, buf); } doadd(buf); while (buf[0] != NSWEPT) { Thread.sleep(1.msecs); } } ```
Dec 01
On 12/1/24 6:23 PM, Andy Valencia wrote:On Monday, 2 December 2024 at 02:02:56 UTC, Ritina wrote:And here's mine which has interesting amount of differences: import core.atomic; import core.thread; import std.concurrency; import std.conv; import std.stdio; // This is the variable that will be incremented collectively. shared int g; // This is the variable that will signal the threads to stop. shared bool stopRequested; // This is the thread function. void modify(int increment) { while (!stopRequested) { g.atomicOp!"+="(increment); } } void main() { // Spawn some threads. enum totalThreads = 3; foreach (i; 0 .. totalThreads) { const increment = (i + 1).to!int; spawnLinked(&modify, increment); } // Wait for a while to request them to stop. Thread.sleep(2.seconds); stopRequested = true; // Wait until all threads are stopped. size_t stopped = 0; while (stopped != totalThreads) { receive((LinkTerminated _) { stopped++; }); } // Print the final value of g. writefln!"%,s"(g); } I am not sure whether stopRequested = true is correct even when there is a single writer of that variable. There are several other methods of communicating the request. I chose that one for this example. AliHow can I implement a program where I have a global integer variable g, and three threads: the first thread increments g by 1, the second thread increments g by 2, and the third thread increments g by 3? Additionally, while these threads are running, I should be able to access the value of g both inside and outside the threads.Here's my own shared memory across multiple threads sample program, It does a fine job of thrashing that cache line! ``` import core.atomic : atomicFetchAdd; import std.concurrency : spawn; import core.time : msecs; import core.thread : Thread; import core.memory : GC; const uint NSWEPT = 100_000_000; const uint NCPU = 4; void doadd(shared uint *buf) { for (uint count = 0; count < NSWEPT/NCPU; ++count) { atomicFetchAdd(buf[0], 1); } } void main() { shared uint *buf = cast(shared uint *)GC.calloc(uint.sizeof * 1, GC.BlkAttr.NO_SCAN); for (uint x = 0; x < NCPU-1; ++x) { spawn(&doadd, buf); } doadd(buf); while (buf[0] != NSWEPT) { Thread.sleep(1.msecs); } } ```
Dec 01
On Monday, 2 December 2024 at 02:29:39 UTC, Ali Çehreli wrote:I am not sure whether stopRequested = true is correct even when there is a single writer of that variable. There are several other methods of communicating the request. I chose that one for this example.I notice core.atomic has an atomicLoad() / atomicStore() pair of APIs which might be the "canonical" way to code in that fashion. Andy
Dec 01
On Monday, 2 December 2024 at 02:29:39 UTC, Ali Çehreli wrote:... There are several other methods of communicating the request..I've slightly edited the code that the AI generates. Ali, how is it now, can we say that it is the best method? ```d import core.thread; import core.atomic; import core.sync.mutex; struct GlobalCounter { private Mutex mutex; private shared int gInt; void initialize(int initialValue = 0) { mutex = new Mutex(); gInt = initialValue; } void increment(int value) { synchronized(mutex) { gInt.atomicOp!"+="(value); } } auto getValue() { synchronized(mutex) { return gInt; } } } enum INC = 100; void incrementInThread(int incrementValue) { for (int i = 0; i < INC; i++) { globalCounter.increment(incrementValue); } } __gshared GlobalCounter globalCounter; void main() { globalCounter.initialize(41); auto threads = [ new Thread(() { incrementInThread(1); }), new Thread(() { incrementInThread(2); }), new Thread(() { incrementInThread(3); }) ]; foreach (ref thread; threads) { thread.start(); thread.join(); } assert(globalCounter.getValue == 641); } ``` SDB 79
Dec 01
You don't need both atomics an mutex's, pick one.
Dec 02
On Monday, 2 December 2024 at 08:00:40 UTC, Richard (Rikki) Andrew Cattermole wrote:You don't need both atomics an mutex's, pick one.The compiler wants us to use atomicOp; If you want, take it out of the synchronized(mutex) { } block, it doesn't matter:onlineapp.d(20): Error: read-modify-write operations are not allowed for `shared` variablesonlineapp.d(20): Use `core.atomic.atomicOp!"+="(this.gInt, value)` instead SDB 79
Dec 02
On 12/2/24 7:18 AM, Salih Dincer wrote:On Monday, 2 December 2024 at 08:00:40 UTC, Richard (Rikki) Andrew Cattermole wrote:Then use atomicOp. You don't need any mutex for this problem, especially when they can make this program very slow. Imagine how a thread will have to relinquish its execution time to wait for a mutex in order to increment a simple int. The CPU's atomic operation primitives already achieve that. Another general reason for avoiding a mutex is that they are low level primitives, which should be needed only in special cases when existing solution that are already based on them don't work for your problem for some reason. For example, std.parallelism and std.concurrency already use features like mutexes but perhaps they can't be used for some reason. AliYou don't need both atomics an mutex's, pick one.The compiler wants us to use atomicOp; If you want, take it out of the synchronized(mutex) { } block, it doesn't matter:
Dec 02
On 03/12/2024 4:18 AM, Salih Dincer wrote:On Monday, 2 December 2024 at 08:00:40 UTC, Richard (Rikki) Andrew Cattermole wrote:That is because you used ``shared``. As a type qualifier/storage class, ``shared`` should be called ``atomic``. If you use it to indicate anything other than the variable can only be accessed/mutated via atomic operations, you are at best lieing to yourself about the native memory model. All memory is owned by the process, until proven otherwise. Which is the exact opposite of what ``shared`` implies.You don't need both atomics an mutex's, pick one.The compiler wants us to use atomicOp; If you want, take it out of the synchronized(mutex) { } block, it doesn't matter:
Dec 02
On Monday, 2 December 2024 at 21:55:55 UTC, Richard (Rikki) Andrew Cattermole wrote:As a type qualifier/storage class, ``shared`` should be called ``atomic``.`shared` is more accurate. Atomic ops are not the only way intended to mutate `shared` data. In fact atomic ops can be slower.If you use it to indicate anything other than the variable can only be accessed/mutated via atomic operations, you are at best lieing to yourself about the native memory model. All memory is owned by the process, until proven otherwise. Which is the exact opposite of what ``shared`` implies.`shared` - shared (i.e. accessible) across threads.
Dec 03
On 04/12/2024 6:37 AM, Nick Treleaven wrote:If you use it to indicate anything other than the variable can only be accessed/mutated via atomic operations, you are at best lieing to yourself about the native memory model. All memory is owned by the process, until proven otherwise. Which is the exact opposite of what |shared| implies. |shared| - shared (i.e. accessible) across threads.That is already true. You don't need a type qualifier to tell you that. What you need the compiler assistance for, is to tell you that it is NOT accessible to multiple threads.
Dec 03
On 12/3/24 9:47 AM, Richard (Rikki) Andrew Cattermole wrote:On 04/12/2024 6:37 AM, Nick Treleaven wrote:That conflicts with my knowledge of data being thread-local by default in D.|shared| - shared (i.e. accessible) across threads.That is already true.You don't need a type qualifier to tell you that. What you need the compiler assistance for, is to tell you that it is NOT accessible to multiple threads.I think you are objecting to D's thread-local by default decision but I'm not sure. :) Ali
Dec 03
On 04/12/2024 11:20 AM, Ali Çehreli wrote:On 12/3/24 9:47 AM, Richard (Rikki) Andrew Cattermole wrote: > On 04/12/2024 6:37 AM, Nick Treleaven wrote: >> |shared| - shared (i.e. accessible) across threads. > > That is already true. That conflicts with my knowledge of data being thread-local by default in D. > You don't need a type qualifier to tell you that. > > What you need the compiler assistance for, is to tell you that it is NOT > accessible to multiple threads. I think you are objecting to D's thread-local by default decision but I'm not sure. :) AliYou are thinking of globals, which are by default TLS yes. No objection to that here. What owned by a thread means is that a pointer is guaranteed to only be accessible by that thread. I.e. the cpu will segfault if you try to access it from another thread. Current CPU's don't offer this protection, so it would have to be proven by the compiler using language features instead.
Dec 03
On Tuesday, 3 December 2024 at 23:16:00 UTC, Richard (Rikki) Andrew Cattermole wrote:What owned by a thread means is that a pointer is guaranteed to only be accessible by that thread. I.e. the cpu will segfault if you try to access it from another thread.My experience is that aside from thread-local module globals, all other objects you create can be subsequently cast to shared and then sent on to another thread. It can be accessed on the receiving thread as such, or cast back to un-shared and used as just another private data structure. In fact, parts of Phobos will not work with objects having a shared attribute. So you have to be careful about transferring ownership for such data structures, and verifying your code path guarantees exclusive access before casting it back to unshared. Andy
Dec 03
On Monday, 2 December 2024 at 02:02:56 UTC, Ritina wrote:How can I implement a program where I have a global integer variable g, and three threads: the first thread increments g by 1, the second thread increments g by 2, and the third thread increments g by 3? Additionally, while these threads are running, I should be able to access the value of g both inside and outside the threads.When we ask claude.ai what is to be done, we get a very clear and great answer. This shows that D is an easy language. Yes, if the result is 600, each thread has increased it 100 times individually: ```d import std.conv, std.stdio; import core.sync.mutex; import core.atomic; import core.thread; struct GlobalCounter { // Mutex to protect shared access to the global variable private Mutex mutex; // The global integer variable to be incremented private shared int gInt; // Method to safely increment the global variable void increment(int value) { synchronized(mutex) { gInt.atomicOp!"+="(value); } } // Getter method to safely read the global variable string toString() { synchronized(mutex) { return gInt.to!string; } } } void main() { // Create a shared counter instance GlobalCounter counter; // Initialize the mutex counter.mutex = new Mutex(); // Create three threads with different increment values auto thread1 = new Thread(() { for (int i = 0; i < 100; i++) { counter.increment(1); } }); auto thread2 = new Thread(() { for (int i = 0; i < 100; i++) { counter.increment(2); } }); auto thread3 = new Thread(() { for (int i = 0; i < 100; i++) { counter.increment(3); } }); // Start all threads thread1.start(); thread2.start(); thread3.start(); // Wait for all threads to complete thread1.join(); thread2.join(); thread3.join(); // Print the final value counter.writefln!"Final value: %s"; } ``` SDB 79
Dec 01