Passing messages with std.concurrency

Passing data to and from threads correctly can be difficult to get right. Phobos provides an abstraction to make passing messages much simpler.

How to do it…

Execute the following steps to pass messages with std.concurrency:

  1. Use the command import std.concurrency;.
  2. Spawn a thread with an independent function. You may pass arguments to the function as long as they can be safely shared across threads. You can pass an instance of 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.
  3. Define a struct to serve as a message to communicate across threads.
  4. Send a message with the send function and your struct from the child thread when it has completed its task.
  5. In the parent thread, use receive with a delegate to handle your message type.
  6. Add additional threads to the program as needed or more message handlers to the receive call if you want to receive more messages.

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);
  });
}

How it works…

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.

Note

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.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.163.13