#!/usr/bin/env dub
/+ dub.sdl:
name "gcd_sync_on_caller_thread"
platforms "osx"
targetPath "build"
+/
/**
* GCD — `dispatch_sync_f` borrows the calling thread; `dispatch_async_f` does not.
*
* A dispatch queue is not a thread. `dispatch_sync_f` does not hand the work
* item to a worker and block: on the fast path libdispatch acquires the queue's
* barrier state and invokes the function *inline on the caller's own thread*
* (`_dispatch_lane_barrier_sync_invoke_and_complete`, `src/queue.c`). Only when
* the queue is already busy does it fall back to enqueuing a waiter and parking
* (`_dispatch_sync_f_slow`). `dispatch_async_f` always runs on a worker thread
* drawn from the kernel's workqueue.
*
* The program also demonstrates the FIFO guarantee of a serial queue, and the
* druntime rule for D code that runs on GCD worker threads: those threads are
* not known to the GC, so a handler must either stay allocation-free or call
* `thread_attachThis()`/`thread_detachThis()` around its body.
*
* Companion to the GCD deep-dive:
* see docs/research/async-io/gcd/index.md § "`dispatch_sync` runs on the caller's thread".
*
* Run with: `dub run --single sync-on-caller-thread.d`
*
* Portability: macOS only (`platforms "osx"`).
*/
module (module) gcd_sync_on_caller_threadGCD — dispatch_sync_f borrows the calling thread; dispatch_async_f does not.
A dispatch queue is not a thread. dispatch_sync_f does not hand the work
item to a worker and block: on the fast path libdispatch acquires the queue's
barrier state and invokes the function inline on the caller's own thread
(_dispatch_lane_barrier_sync_invoke_and_complete, src/queue.c). Only when
the queue is already busy does it fall back to enqueuing a waiter and parking
(_dispatch_sync_f_slow). dispatch_async_f always runs on a worker thread
drawn from the kernel's workqueue.
The program also demonstrates the FIFO guarantee of a serial queue, and the
druntime rule for D code that runs on GCD worker threads: those threads are
not known to the GC, so a handler must either stay allocation-free or call
thread_attachThis()/thread_detachThis() around its body.
Companion to the GCD deep-dive:
see docs/research/async-io/gcd/index.md § "dispatch_sync runs on the caller's thread".
Run with: dub run --single sync-on-caller-thread.d
Portability
macOS only (platforms "osx").
gcd_sync_on_caller_thread;
import (package) corecore.(module) core.atomicThe atomic module provides basic support for lock-free
concurrent programming.
Use the -preview=nosharedaccess compiler flag to detect
unsafe individual read or write operations on shared data.
Source
core/atomic.d
Examples
int y = 2;
shared int x = y; // OK
//x++; // read modify write error
x.atomicOp!"+="(1); // OK
//y = x; // read error with preview flag
y = x.atomicLoad(); // OK
assert(y == 3);
//x = 5; // write error with preview flag
x.atomicStore(5); // OK
assert(x.atomicLoad() == 5);
atomic : (alias template) gcd_sync_on_caller_thread.atomicLoad = core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))Loads 'val' from memory and returns it. The memory barrier specified
by 'ms' is applied to the operation, which is fully sequenced by
default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq,
and MemoryOrder.seq.
atomicLoad, (alias template) gcd_sync_on_caller_thread.atomicStore = core.atomic.atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) if (!is(T == shared) && !is(V == shared))Writes 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdintD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdint.h.html, stdint.h
Source
core/stdc/stdint.d
stdint : uintptr_t;
import (package) corecore.(package) core.threadthread.(module) core.thread.osthreadThe osthread module provides low-level, OS-dependent code
for thread creation and management.
Source
core/thread/osthread.d
osthread : (alias) gcd_sync_on_caller_thread.thread_attachThis = core.thread.osthread.Thread core.thread.osthread.thread_attachThis()Registers the calling thread for use with the D Runtime. If this routine
is called for a thread which is already registered, no action is performed.
NOTE
This routine does not run thread-local static constructors when called.
If full functionality as a D thread is desired, the following function
must be called after thread_attachThis:
extern (C) void rt_moduleTlsCtor();
thread_attachThis;
import (package) corecore.(package) core.threadthread.(module) core.thread.threadbaseThe threadbase module provides OS-independent code
for thread storage and management.
Source
core/thread/threadbase.d
threadbase : (alias) gcd_sync_on_caller_thread.thread_detachThis = void core.thread.threadbase.thread_detachThis() nothrow @nogcDeregisters the calling thread from use with the runtime. If this routine
is called for a thread which is not registered, the result is undefined.
Once the thread is removed from the runtime, it must not use the GC because
it does not participate in the Stop-The-World mechanisms. With the default
GC, that has a global lock, this might not cause races, but in GCs with
regional locks, it definitely can cause races.
NOTE
This routine does not run thread-local static destructors when called.
If full functionality as a D thread is desired, the following function
must be called before thread_detachThis, particularly if the thread is
being detached at some indeterminate time before program termination:
extern(C) void rt_moduleTlsDtor();
This also does not call the GC thread cleanup routine. After running
module dtors, it is recommended to call
gc_getProxy().cleanupThread(Thread.getThis());
thread_detachThis;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) gcd_sync_on_caller_thread.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, (alias template) gcd_sync_on_caller_thread.writeln = std.stdio.writeln(T...)(T args)Equivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln;
alias (alias) gcd_sync_on_caller_thread.dispatch_queue_t = void*dispatch_queue_t = void*;
alias (alias) gcd_sync_on_caller_thread.dispatch_semaphore_t = void*dispatch_semaphore_t = void*;
alias (alias) gcd_sync_on_caller_thread.dispatch_function_t = extern (C) void function(void*) nothrowdispatch_function_t = extern (C) void function(void*) nothrow;
extern (C) nothrow @nogc
{
(alias) gcd_sync_on_caller_thread.dispatch_queue_t = void*dispatch_queue_t void* gcd_sync_on_caller_thread.dispatch_queue_create(const(char)* label, void* attr) nothrow @nogcdispatch_queue_create(const(char)* (parameter) const(char)* labellabel, void* (parameter) void* attrattr);
void void gcd_sync_on_caller_thread.dispatch_sync_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogcdispatch_sync_f((alias) gcd_sync_on_caller_thread.dispatch_queue_t = void*dispatch_queue_t (parameter) void* queuequeue, void* (parameter) void* contextcontext, (alias) gcd_sync_on_caller_thread.dispatch_function_t = extern (C) void function(void*) nothrowdispatch_function_t (parameter) extern (C) void function(void*) nothrow workwork);
void void gcd_sync_on_caller_thread.dispatch_async_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogcdispatch_async_f((alias) gcd_sync_on_caller_thread.dispatch_queue_t = void*dispatch_queue_t (parameter) void* queuequeue, void* (parameter) void* contextcontext, (alias) gcd_sync_on_caller_thread.dispatch_function_t = extern (C) void function(void*) nothrowdispatch_function_t (parameter) extern (C) void function(void*) nothrow workwork);
void void gcd_sync_on_caller_thread.dispatch_release(void* object) nothrow @nogcdispatch_release(void* (parameter) void* objectobject);
(alias) gcd_sync_on_caller_thread.dispatch_semaphore_t = void*dispatch_semaphore_t void* gcd_sync_on_caller_thread.dispatch_semaphore_create(long value) nothrow @nogcdispatch_semaphore_create(long (parameter) long valuevalue);
long long gcd_sync_on_caller_thread.dispatch_semaphore_wait(void* sema, ulong timeout) nothrow @nogcdispatch_semaphore_wait((alias) gcd_sync_on_caller_thread.dispatch_semaphore_t = void*dispatch_semaphore_t (parameter) void* semasema, ulong (parameter) ulong timeouttimeout);
long long gcd_sync_on_caller_thread.dispatch_semaphore_signal(void* sema) nothrow @nogcdispatch_semaphore_signal((alias) gcd_sync_on_caller_thread.dispatch_semaphore_t = void*dispatch_semaphore_t (parameter) void* semasema);
// pthread_self is only ever compared here, never dereferenced.
void* void* gcd_sync_on_caller_thread.pthread_self() nothrow @nogcpthread_self();
}
enum (constant) ulong gcd_sync_on_caller_thread.DISPATCH_TIME_FOREVER = 18446744073709551615LUDISPATCH_TIME_FOREVER = ~0UL;
struct (struct) gcd_sync_on_caller_thread.ProbeProbe
{
shared (alias) object.size_t = ulongsize_t (field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThreadobservedThread;
shared int (field) shared(int) gcd_sync_on_caller_thread.Probe.orderingordering; // decimal digits appended in completion order
shared bool (field) shared(bool) gcd_sync_on_caller_thread.Probe.attachedattached;
(alias) gcd_sync_on_caller_thread.dispatch_semaphore_t = void*dispatch_semaphore_t (field) void* gcd_sync_on_caller_thread.Probe.donedone;
}
__gshared (struct) gcd_sync_on_caller_thread.ProbeProbe (__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe;
/// Records which thread ran it. Allocation-free, so it is safe on any thread.
extern (C) void void gcd_sync_on_caller_thread.recordThread(void* context) nothrowRecords which thread ran it. Allocation-free, so it is safe on any thread.
recordThread(void* (parameter) void* contextcontext) nothrow
{
void core.atomic.atomicStore!(MemoryOrder.seq, ulong, ulong)(ref shared(ulong) val, ulong newval) pure nothrow @nogc @trustedWrites 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThreadobservedThread, cast((alias) object.size_t = ulongsize_t) void* gcd_sync_on_caller_thread.pthread_self() nothrow @nogcpthread_self());
}
/// Same, plus the druntime attach dance: after `thread_attachThis()` this
/// worker thread is a first-class D thread and may allocate from the GC.
extern (C) void void gcd_sync_on_caller_thread.recordThreadAndAllocate(void* context) nothrowSame, plus the druntime attach dance: after thread_attachThis() this
worker thread is a first-class D thread and may allocate from the GC.
recordThreadAndAllocate(void* (parameter) void* contextcontext) nothrow
{
void core.atomic.atomicStore!(MemoryOrder.seq, ulong, ulong)(ref shared(ulong) val, ulong newval) pure nothrow @nogc @trustedWrites 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThreadobservedThread, cast((alias) object.size_t = ulongsize_t) void* gcd_sync_on_caller_thread.pthread_self() nothrow @nogcpthread_self());
// A D exception must never unwind into libdispatch's C frames, so the whole
// body is caught here. `thread_attachThis` is itself not `nothrow`.
try
{
core.thread.osthread.Thread core.thread.osthread.thread_attachThis()Registers the calling thread for use with the D Runtime. If this routine
is called for a thread which is already registered, no action is performed.
NOTE
This routine does not run thread-local static constructors when called.
If full functionality as a D thread is desired, the following function
must be called after thread_attachThis:
extern (C) void rt_moduleTlsCtor();
thread_attachThis();
scope (exit)
void core.thread.threadbase.thread_detachThis() nothrow @nogcDeregisters the calling thread from use with the runtime. If this routine
is called for a thread which is not registered, the result is undefined.
Once the thread is removed from the runtime, it must not use the GC because
it does not participate in the Stop-The-World mechanisms. With the default
GC, that has a global lock, this might not cause races, but in GCs with
regional locks, it definitely can cause races.
NOTE
This routine does not run thread-local static destructors when called.
If full functionality as a D thread is desired, the following function
must be called before thread_detachThis, particularly if the thread is
being detached at some indeterminate time before program termination:
extern(C) void rt_moduleTlsDtor();
This also does not call the GC thread cleanup routine. After running
module dtors, it is recommended to call
gc_getProxy().cleanupThread(Thread.getThis());
thread_detachThis();
// Legal only because of the attach above.
auto (local variable) int[] scratchscratch = new int[16];
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trustedWrites 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(bool) gcd_sync_on_caller_thread.Probe.attachedattached, (local variable) int[] scratchscratch.(field) ulong int[].lengthlength == 16);
}
catch ((class) object.ThrowableThe base class of all thrown objects.
All thrown objects must inherit from Throwable. Class Exception, which
derives from this class, represents the category of thrown objects that are
safe to catch and handle. In principle, one should not catch Throwable
objects that are not derived from Exception, as they represent
unrecoverable runtime errors. Certain runtime guarantees may fail to hold
when these errors are thrown, making it unsafe to continue execution after
catching them.
Throwable)
{
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trustedWrites 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(bool) gcd_sync_on_caller_thread.Probe.attachedattached, false);
}
long gcd_sync_on_caller_thread.dispatch_semaphore_signal(void* sema) nothrow @nogcdispatch_semaphore_signal((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) void* gcd_sync_on_caller_thread.Probe.donedone);
}
/// Appends its index (passed as an integer-in-a-pointer) to `ordering`.
extern (C) void void gcd_sync_on_caller_thread.appendDigit(void* context) nothrowAppends its index (passed as an integer-in-a-pointer) to ordering.
appendDigit(void* (parameter) void* contextcontext) nothrow
{
import (package) corecore.(module) core.atomicThe atomic module provides basic support for lock-free
concurrent programming.
Use the -preview=nosharedaccess compiler flag to detect
unsafe individual read or write operations on shared data.
Source
core/atomic.d
Examples
int y = 2;
shared int x = y; // OK
//x++; // read modify write error
x.atomicOp!"+="(1); // OK
//y = x; // read error with preview flag
y = x.atomicLoad(); // OK
assert(y == 3);
//x = 5; // write error with preview flag
x.atomicStore(5); // OK
assert(x.atomicLoad() == 5);
atomic : (alias template) atomicOp = core.atomic.atomicOp(string op, T, V1)(ref shared T val, V1 mod) if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod")))Performs the binary operation 'op' on val using 'mod' as the modifier.
Params:
val = The target variable.
mod = The modifier to apply.
Returns:
The result of the operation.
atomicOp;
int core.atomic.atomicOp!("+=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safePerforms the binary operation 'op' on val using 'mod' as the modifier.
atomicOp!"+="((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(int) gcd_sync_on_caller_thread.Probe.orderingordering, cast(int)(cast(uintptr_t) (parameter) void* contextcontext));
int core.atomic.atomicOp!("*=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safePerforms the binary operation 'op' on val using 'mod' as the modifier.
atomicOp!"*="((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(int) gcd_sync_on_caller_thread.Probe.orderingordering, 10);
}
int int D main()main()
{
auto (local variable) void* queuequeue = void* gcd_sync_on_caller_thread.dispatch_queue_create(const(char)* label, void* attr) nothrow @nogcdispatch_queue_create("dev.sparkles.research.gcd.sync", null);
scope (exit)
void gcd_sync_on_caller_thread.dispatch_release(void* object) nothrow @nogcdispatch_release((local variable) void* queuequeue);
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) void* gcd_sync_on_caller_thread.Probe.donedone = void* gcd_sync_on_caller_thread.dispatch_semaphore_create(long value) nothrow @nogcdispatch_semaphore_create(0);
const (local variable) const(ulong) callercaller = cast((alias) object.size_t = ulongsize_t) void* gcd_sync_on_caller_thread.pthread_self() nothrow @nogcpthread_self();
// 1. dispatch_sync_f on an idle serial queue: invoked inline.
void gcd_sync_on_caller_thread.dispatch_sync_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogcdispatch_sync_f((local variable) void* queuequeue, null, &void gcd_sync_on_caller_thread.recordThread(void* context) nothrowRecords which thread ran it. Allocation-free, so it is safe on any thread.
recordThread);
const (local variable) const(ulong) syncThreadsyncThread = ulong core.atomic.atomicLoad!(MemoryOrder.seq, ulong)(ref return scope shared(const(ulong)) val) pure nothrow @nogc @trustedLoads 'val' from memory and returns it. The memory barrier specified
by 'ms' is applied to the operation, which is fully sequenced by
default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq,
and MemoryOrder.seq.
atomicLoad((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThreadobservedThread);
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("dispatch_sync_f ran on the calling thread: %s", (local variable) const(ulong) syncThreadsyncThread == (local variable) const(ulong) callercaller);
assert((local variable) const(ulong) syncThreadsyncThread == (local variable) const(ulong) callercaller, "dispatch_sync_f did not use the caller's thread");
// 2. dispatch_async_f: always a workqueue thread.
void core.atomic.atomicStore!(MemoryOrder.seq, ulong, int)(ref shared(ulong) val, int newval) pure nothrow @nogc @trustedWrites 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThreadobservedThread, 0);
void gcd_sync_on_caller_thread.dispatch_async_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogcdispatch_async_f((local variable) void* queuequeue, null, &void gcd_sync_on_caller_thread.recordThreadAndAllocate(void* context) nothrowSame, plus the druntime attach dance: after thread_attachThis() this
worker thread is a first-class D thread and may allocate from the GC.
recordThreadAndAllocate);
long gcd_sync_on_caller_thread.dispatch_semaphore_wait(void* sema, ulong timeout) nothrow @nogcdispatch_semaphore_wait((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) void* gcd_sync_on_caller_thread.Probe.donedone, (constant) ulong gcd_sync_on_caller_thread.DISPATCH_TIME_FOREVER = 18446744073709551615LUDISPATCH_TIME_FOREVER);
const (local variable) const(ulong) asyncThreadasyncThread = ulong core.atomic.atomicLoad!(MemoryOrder.seq, ulong)(ref return scope shared(const(ulong)) val) pure nothrow @nogc @trustedLoads 'val' from memory and returns it. The memory barrier specified
by 'ms' is applied to the operation, which is fully sequenced by
default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq,
and MemoryOrder.seq.
atomicLoad((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThreadobservedThread);
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("dispatch_async_f ran on the calling thread: %s", (local variable) const(ulong) asyncThreadasyncThread == (local variable) const(ulong) callercaller);
assert((local variable) const(ulong) asyncThreadasyncThread != (local variable) const(ulong) callercaller, "dispatch_async_f reused the caller's thread");
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("worker thread could allocate after thread_attachThis: %s", bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trustedLoads 'val' from memory and returns it. The memory barrier specified
by 'ms' is applied to the operation, which is fully sequenced by
default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq,
and MemoryOrder.seq.
atomicLoad((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(bool) gcd_sync_on_caller_thread.Probe.attachedattached));
assert(bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trustedLoads 'val' from memory and returns it. The memory barrier specified
by 'ms' is applied to the operation, which is fully sequenced by
default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq,
and MemoryOrder.seq.
atomicLoad((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(bool) gcd_sync_on_caller_thread.Probe.attachedattached), "GC allocation on an attached worker thread failed");
// 3. A serial queue dequeues in FIFO order, whichever thread each item lands
// on: the digits 1..5 accumulate as 12345 followed by the trailing zero
// of the last multiply.
void core.atomic.atomicStore!(MemoryOrder.seq, int, int)(ref shared(int) val, int newval) pure nothrow @nogc @trustedWrites 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(int) gcd_sync_on_caller_thread.Probe.orderingordering, 0);
foreach ((local variable) int ii; 1 .. 6)
void gcd_sync_on_caller_thread.dispatch_async_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogcdispatch_async_f((local variable) void* queuequeue, cast(void*) cast(uintptr_t) (local variable) int ii, &void gcd_sync_on_caller_thread.appendDigit(void* context) nothrowAppends its index (passed as an integer-in-a-pointer) to ordering.
appendDigit);
void gcd_sync_on_caller_thread.dispatch_sync_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogcdispatch_sync_f((local variable) void* queuequeue, null, &void gcd_sync_on_caller_thread.recordThread(void* context) nothrowRecords which thread ran it. Allocation-free, so it is safe on any thread.
recordThread); // barrier: drains everything before it
const (local variable) const(int) orderingordering = int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trustedLoads 'val' from memory and returns it. The memory barrier specified
by 'ms' is applied to the operation, which is fully sequenced by
default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq,
and MemoryOrder.seq.
atomicLoad((__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probeprobe.(field) shared(int) gcd_sync_on_caller_thread.Probe.orderingordering);
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("serial queue completion order: %d", (local variable) const(int) orderingordering);
assert((local variable) const(int) orderingordering == 123_450, "serial queue did not run its work items in FIFO order");
void std.stdio.writeln!()() @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln();
void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln("a queue is a lane, not a thread: `sync` borrows one, `async` rents one");
return 0;
}