Passing data to and from threads correctly can be difficult to get right. Phobos provides an abstraction to make passing messages much simpler.
Execute the following steps to pass messages with std.concurrency
:
import std.concurrency;
.thisTid
to the child thread so that it knows who its parent is for communication, or the child can use std.concurrency.ownerTid
to get the handle of the thread that spawned it.send
function and your struct from the child thread when it has completed its task.receive
with a delegate to handle your message type.Review the following code:
import std.concurrency; struct MyMessage { int a; } void writeToFile(string filename, string text) { import std.stdio; auto file = File(filename, "wt"); file.write(text); } void doSomething(Tid parent, Duration sleepDuration) { import core.thread; // for sleep Thread.sleep(sleepDuration); send(parent, MyMessage(sleepDuration)); } void main() { // writing to files can be slow, let's spawn a thread // so the rest of the program doesn't have to wait spawn(&writeToFile, "text.txt", "writing some text"); // we'll also make another worker thread to do a slow // operation auto tid = spawn(&doSomething, thisTid, 3.seconds); // we'll now wait until the child sends us a message receive((MyMessage msg) { import std.stdio; writeln("Got my message: ", msg.a); }); }
The std.concurrency
feature builds on the core.thread
model to provide an easy-to-use and correct-by-default message passing API. The key functions for the basic operation are spawn
, send
, receive
, and thisTid
.
The spawn
function takes a pointer to a function, just like new Thread()
, and then takes a list of arguments to pass to that function. Among the arguments we passed was thisTid
, a reference to the current thread for use with message passing. Remember, the main thread is created automatically, so a Tid
is always available, even before spawning any new thread. The spawn
function automatically starts the new thread and returns its Tid
, which can be used to send messages to it from the parent thread.
The spawn
function will statically reject any data that may be written from two threads at a time. This rejects mutable arrays, but does not include any immutable
data or value types such as integers. To spawn a thread with a mutable array, it must be explicitly marked as shared
in both the variable declaration and the function argument lists.
The send
function sends a message to the specified thread, identified by its Tid
. The message is plain data, identified by its type. The implementation will append the message to a thread-local mailbox in the target thread until it is handled by calling receive
.
Similar to user-defined attributes, you can also send plain data, such as built-in strings; however, I recommend against this because a basic type can mean anything without looking at the context. By always using a user-defined struct for messages, the intended usage can be made clear on both the send and receive sides.
The receive
function performs type matching against a list of delegates. You give it a list of handlers for various message types. The type of the argument is the message it handles. Message type matching is performed from the first delegate given to receive
to the last, with the first match being called. Any unreachable handler, for example, a repeated type or a more specific type after a more generic handler, will cause a compile-time error. A handler that takes an argument of type std.variant.Variant
acts as a catch-all for messages.
If you want two-way communication, you can include a Tid from;
field in the message struct, which the receive
handler can then use to send a reply. The parent thread may run receive
in a loop to process several messages.
18.221.163.13