sync-on-caller-thread.dhover×172all
#!/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_thread

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").

gcd_sync_on_caller_thread
;
import
(package) core
core
.
(module) core.atomic

The 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);
@copyrightCopyright Sean Kelly 2005 - 2016.@licenseBoost License 1.0@authorsSean Kelly, Alex Rønne Petersen, Manu Evans
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.

@paramval The target variable.@returnsThe value of 'val'.
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.

@paramval The target variable.@paramnewval The value to store.
atomicStore
;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.stdint

D header file for C99.

pubs.opengroup.org/onlinepubs/009695399/basedefs/stdint.h.html, stdint.h

Source

core/stdc/stdint.d

@copyrightCopyright Sean Kelly 2005 - 2018@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly@standardsISO/IEC 9899:1999 (E)
stdint
: uintptr_t;
import
(package) core
core
.
(package) core.thread
thread
.
(module) core.thread.osthread

The osthread module provides low-level, OS-dependent code for thread creation and management.

Source

core/thread/osthread.d

@copyrightCopyright Sean Kelly 2005 - 2012.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak
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();

@seethread_detachThis
thread_attachThis
;
import
(package) core
core
.
(package) core.thread
thread
.
(module) core.thread.threadbase

The threadbase module provides OS-independent code for thread storage and management.

Source

core/thread/threadbase.d

@copyrightCopyright Sean Kelly 2005 - 2012.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak
threadbase
:
(alias) gcd_sync_on_caller_thread.thread_detachThis = void core.thread.threadbase.thread_detachThis() nothrow @nogc

Deregisters 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());

@seethread_attachThis
thread_detachThis
;
import
(package) std
std
.
(module) std.stdio
Category 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:

  1. The lowest layer is the operating system layer. The two main schemes are Windows and Posix.

  2. C's stdio.h which unifies the two operating system schemes.

  3. std.stdio, this module, unifies the various stdio.h implementations into a high level package for D programs.

Source

std/stdio.d

@copyrightCopyright The D Language Foundation 2007-.@licenseBoost License 1.0.@authorsWalter Bright, Andrei Alexandrescu, Alex Rønne Petersen
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);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
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*) nothrow
dispatch_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 @nogc
dispatch_queue_create
(const(char)*
(parameter) const(char)* label
label
, void*
(parameter) void* attr
attr
);
void
void gcd_sync_on_caller_thread.dispatch_sync_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogc
dispatch_sync_f
(
(alias) gcd_sync_on_caller_thread.dispatch_queue_t = void*
dispatch_queue_t
(parameter) void* queue
queue
, void*
(parameter) void* context
context
,
(alias) gcd_sync_on_caller_thread.dispatch_function_t = extern (C) void function(void*) nothrow
dispatch_function_t
(parameter) extern (C) void function(void*) nothrow work
work
);
void
void gcd_sync_on_caller_thread.dispatch_async_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogc
dispatch_async_f
(
(alias) gcd_sync_on_caller_thread.dispatch_queue_t = void*
dispatch_queue_t
(parameter) void* queue
queue
, void*
(parameter) void* context
context
,
(alias) gcd_sync_on_caller_thread.dispatch_function_t = extern (C) void function(void*) nothrow
dispatch_function_t
(parameter) extern (C) void function(void*) nothrow work
work
);
void
void gcd_sync_on_caller_thread.dispatch_release(void* object) nothrow @nogc
dispatch_release
(void*
(parameter) void* object
object
);
(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 @nogc
dispatch_semaphore_create
(long
(parameter) long value
value
);
long
long gcd_sync_on_caller_thread.dispatch_semaphore_wait(void* sema, ulong timeout) nothrow @nogc
dispatch_semaphore_wait
(
(alias) gcd_sync_on_caller_thread.dispatch_semaphore_t = void*
dispatch_semaphore_t
(parameter) void* sema
sema
, ulong
(parameter) ulong timeout
timeout
);
long
long gcd_sync_on_caller_thread.dispatch_semaphore_signal(void* sema) nothrow @nogc
dispatch_semaphore_signal
(
(alias) gcd_sync_on_caller_thread.dispatch_semaphore_t = void*
dispatch_semaphore_t
(parameter) void* sema
sema
);
// pthread_self is only ever compared here, never dereferenced. void*
void* gcd_sync_on_caller_thread.pthread_self() nothrow @nogc
pthread_self
();
} enum
(constant) ulong gcd_sync_on_caller_thread.DISPATCH_TIME_FOREVER = 18446744073709551615LU
DISPATCH_TIME_FOREVER
= ~0UL;
struct
(struct) gcd_sync_on_caller_thread.Probe
Probe
{ shared
(alias) object.size_t = ulong
size_t
(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThread
observedThread
;
shared int
(field) shared(int) gcd_sync_on_caller_thread.Probe.ordering
ordering
; // decimal digits appended in completion order
shared bool
(field) shared(bool) gcd_sync_on_caller_thread.Probe.attached
attached
;
(alias) gcd_sync_on_caller_thread.dispatch_semaphore_t = void*
dispatch_semaphore_t
(field) void* gcd_sync_on_caller_thread.Probe.done
done
;
} __gshared
(struct) gcd_sync_on_caller_thread.Probe
Probe
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
;
/// 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) nothrow

Records which thread ran it. Allocation-free, so it is safe on any thread.

recordThread
(void*
(parameter) void* context
context
) nothrow
{
void core.atomic.atomicStore!(MemoryOrder.seq, ulong, ulong)(ref shared(ulong) val, ulong newval) pure nothrow @nogc @trusted

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.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThread
observedThread
, cast(
(alias) object.size_t = ulong
size_t
)
void* gcd_sync_on_caller_thread.pthread_self() nothrow @nogc
pthread_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) nothrow

Same, 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* context
context
) nothrow
{
void core.atomic.atomicStore!(MemoryOrder.seq, ulong, ulong)(ref shared(ulong) val, ulong newval) pure nothrow @nogc @trusted

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.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThread
observedThread
, cast(
(alias) object.size_t = ulong
size_t
)
void* gcd_sync_on_caller_thread.pthread_self() nothrow @nogc
pthread_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();

@seethread_detachThis
thread_attachThis
();
scope (exit)
void core.thread.threadbase.thread_detachThis() nothrow @nogc

Deregisters 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());

@seethread_attachThis
thread_detachThis
();
// Legal only because of the attach above. auto
(local variable) int[] scratch
scratch
= new int[16];
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

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.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(bool) gcd_sync_on_caller_thread.Probe.attached
attached
,
(local variable) int[] scratch
scratch
.
(field) ulong int[].length
length
== 16);
} catch (
(class) object.Throwable

The 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 @trusted

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.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(bool) gcd_sync_on_caller_thread.Probe.attached
attached
, false);
}
long gcd_sync_on_caller_thread.dispatch_semaphore_signal(void* sema) nothrow @nogc
dispatch_semaphore_signal
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) void* gcd_sync_on_caller_thread.Probe.done
done
);
} /// Appends its index (passed as an integer-in-a-pointer) to `ordering`. extern (C) void
void gcd_sync_on_caller_thread.appendDigit(void* context) nothrow

Appends its index (passed as an integer-in-a-pointer) to ordering.

appendDigit
(void*
(parameter) void* context
context
) nothrow
{ import
(package) core
core
.
(module) core.atomic

The 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);
@copyrightCopyright Sean Kelly 2005 - 2016.@licenseBoost License 1.0@authorsSean Kelly, Alex Rønne Petersen, Manu Evans
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 @safe

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
!"+="(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(int) gcd_sync_on_caller_thread.Probe.ordering
ordering
, cast(int)(cast(uintptr_t)
(parameter) void* context
context
));
int core.atomic.atomicOp!("*=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safe

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
!"*="(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(int) gcd_sync_on_caller_thread.Probe.ordering
ordering
, 10);
} int
int D main()
main
()
{ auto
(local variable) void* queue
queue
=
void* gcd_sync_on_caller_thread.dispatch_queue_create(const(char)* label, void* attr) nothrow @nogc
dispatch_queue_create
("dev.sparkles.research.gcd.sync", null);
scope (exit)
void gcd_sync_on_caller_thread.dispatch_release(void* object) nothrow @nogc
dispatch_release
(
(local variable) void* queue
queue
);
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) void* gcd_sync_on_caller_thread.Probe.done
done
=
void* gcd_sync_on_caller_thread.dispatch_semaphore_create(long value) nothrow @nogc
dispatch_semaphore_create
(0);
const
(local variable) const(ulong) caller
caller
= cast(
(alias) object.size_t = ulong
size_t
)
void* gcd_sync_on_caller_thread.pthread_self() nothrow @nogc
pthread_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 @nogc
dispatch_sync_f
(
(local variable) void* queue
queue
, null, &
void gcd_sync_on_caller_thread.recordThread(void* context) nothrow

Records which thread ran it. Allocation-free, so it is safe on any thread.

recordThread
);
const
(local variable) const(ulong) syncThread
syncThread
=
ulong core.atomic.atomicLoad!(MemoryOrder.seq, ulong)(ref return scope shared(const(ulong)) val) pure nothrow @nogc @trusted

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.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThread
observedThread
);
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("dispatch_sync_f ran on the calling thread: %s",
(local variable) const(ulong) syncThread
syncThread
==
(local variable) const(ulong) caller
caller
);
assert(
(local variable) const(ulong) syncThread
syncThread
==
(local variable) const(ulong) caller
caller
, "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 @trusted

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.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThread
observedThread
, 0);
void gcd_sync_on_caller_thread.dispatch_async_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogc
dispatch_async_f
(
(local variable) void* queue
queue
, null, &
void gcd_sync_on_caller_thread.recordThreadAndAllocate(void* context) nothrow

Same, 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 @nogc
dispatch_semaphore_wait
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) void* gcd_sync_on_caller_thread.Probe.done
done
,
(constant) ulong gcd_sync_on_caller_thread.DISPATCH_TIME_FOREVER = 18446744073709551615LU
DISPATCH_TIME_FOREVER
);
const
(local variable) const(ulong) asyncThread
asyncThread
=
ulong core.atomic.atomicLoad!(MemoryOrder.seq, ulong)(ref return scope shared(const(ulong)) val) pure nothrow @nogc @trusted

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.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(ulong) gcd_sync_on_caller_thread.Probe.observedThread
observedThread
);
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("dispatch_async_f ran on the calling thread: %s",
(local variable) const(ulong) asyncThread
asyncThread
==
(local variable) const(ulong) caller
caller
);
assert(
(local variable) const(ulong) asyncThread
asyncThread
!=
(local variable) const(ulong) caller
caller
, "dispatch_async_f reused the caller's thread");
void std.stdio.writefln!(char, bool)(in char[] fmt, bool __param_1) @safe

Equivalent 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 @trusted

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.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(bool) gcd_sync_on_caller_thread.Probe.attached
attached
));
assert(
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

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.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(bool) gcd_sync_on_caller_thread.Probe.attached
attached
), "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 @trusted

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.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(int) gcd_sync_on_caller_thread.Probe.ordering
ordering
, 0);
foreach (
(local variable) int i
i
; 1 .. 6)
void gcd_sync_on_caller_thread.dispatch_async_f(void* queue, void* context, extern (C) void function(void*) nothrow work) nothrow @nogc
dispatch_async_f
(
(local variable) void* queue
queue
, cast(void*) cast(uintptr_t)
(local variable) int i
i
, &
void gcd_sync_on_caller_thread.appendDigit(void* context) nothrow

Appends 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 @nogc
dispatch_sync_f
(
(local variable) void* queue
queue
, null, &
void gcd_sync_on_caller_thread.recordThread(void* context) nothrow

Records which thread ran it. Allocation-free, so it is safe on any thread.

recordThread
); // barrier: drains everything before it
const
(local variable) const(int) ordering
ordering
=
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

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.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(__gshared global) gcd_sync_on_caller_thread.Probe gcd_sync_on_caller_thread.probe
probe
.
(field) shared(int) gcd_sync_on_caller_thread.Probe.ordering
ordering
);
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("serial queue completion order: %d",
(local variable) const(int) ordering
ordering
);
assert(
(local variable) const(int) ordering
ordering
== 123_450, "serial queue did not run its work items in FIFO order");
void std.stdio.writeln!()() @safe

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);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
();
void std.stdio.writeln!string(string __param_0) @safe

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);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
("a queue is a lane, not a thread: `sync` borrows one, `async` rents one");
return 0; }