Threads are a common building block for multitasking inside a single process. A thread is an independent stream of program execution that has access to the memory of other threads.
To start using threads, we will have to execute the following steps:
import core.thread;
.Thread.sleep
from the thread to be paused.auto thread = new Thread
, passing it as a pointer to your function.thread.start
to begin execution.Take a look at the following code:
import core.thread; import core.atomic; import std.stdio; int count = 0; shared(int) sharedCount = 0; // this is the function that will run in our threads void threadedFunction() { foreach(i; 0 .. 5) { writeln("thread running ", count++, " global: ", sharedCount); Thread.sleep(1.seconds); atomicOp!"+="(sharedCount, 1); } } void main() { auto thread = new Thread(&threadedFunction); thread.start(); Thread.sleep(1500.msecs); auto thread2 = new Thread(&threadedFunction); thread2.start(); }
Running the program will result in the following interleaved output:
thread running 0 global: 0 thread running 1 global: 1 thread running 0 global: 1 thread running 2 global: 2 thread running 1 global: 3 thread running 3 global: 4 thread running 2 global: 5 thread running 4 global: 6 thread running 3 global: 7 thread running 4 global: 9
The D runtime library encapsulates threads in a Thread
class, one of which is created automatically when a program starts up, representing the program's main thread of execution. So, any operation that can be done by a child thread can also be done by the main thread, such as Thread.sleep
, which pauses execution for the specified duration.
A thread's constructor takes a pointer to a function, which they execute to completion. This is similar to how the program as a whole runs main
and terminates when it returns. A new thread is created in the paused state. To begin its execution, we must call its start
method.
All started threads will run simultaneously and automatically spread out across multiple processor cores, if available. Multiple threads running simultaneously means nonatomic operations (that is, operations that consist of more than one machine instruction) may be interrupted at any time. Thus, working with shared data must be done carefully.
To alleviate the difficulty of working with shared data, D takes a different approach than most other languages. Whereas in C, for example, all global and static variables are assumed to be shared unless specifically marked as __thread
, in D, all global and static variables are assumed to be thread-local unless specifically marked as shared
. Shared then becomes part of the variable's type, forcing you to think about the ramifications of sharing data between threads before you use it.
Our example program had both count
and sharedCount
to illustrate the difference. The count
variable is thread-local, so each thread has an independent count and the variable can be used normally. The sharedCount
variable, on the other hand, is shared across all threads and needs to be properly synchronized to avoid race conditions (where one thread reads a value, another thread writes a value, and the first thread writes back the old value, saving over the second thread's write).
To address this problem, instead of using the regular increment operator, we used core.atomic.atomicOp
. An atomic operation is one that cannot be broken down into interruptible pieces—it is done all at once, without giving another thread a chance to intervene before it is finished. atomicOp
takes an operation as a compile-time string argument, a piece of shared data to act upon, and the value by which to modify it.
In the next recipe, we'll look at a higher level of abstraction on top of the low-level threads seen here, which simplifies some of the details of data sharing.
Threads in D, by default, are known to the garbage collector and will prevent the termination of the program until they are all completed.
To create a thread that doesn't prevent process termination, you want a daemon thread. Creating a daemon thread is easy: simply create a regular Thread
and then set the isDaemon
property to true
, as shown in the following snippet:
auto thread = new Thread(&threadedFunction); thread.isDaemon = true; // make it a daemon thread thread.start();
Now, if main
returns before this thread is complete, the process will terminate immediately without waiting for the thread to finish.
Creating a thread that is not known to the garbage collector is more difficult and very tricky to get right. To do this, you will have to create a thread using operating system primitives like you would in C instead of the D library. Doing this is not recommended and should only be used when you are certain about what you are doing.
Thread.sleep
, is http://dlang.org/phobos/core_time.html18.217.199.122