authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-04 22:31:02-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-07-04 22:31:02-04:00
logb7da1b2d45bc42a56eea3a143e4237a0712c4769
tree5474938657d5dfd9273562c160ad5f1e3a02b824
parent5d0dad9acdac854d68e1447b90fd3dbde9ff0b2d
parentc8f90a7e7e10be62634454bf124bef3c6130a0db
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #9175 from kprotty/thread

std.Thread enhancements

19 files changed, 898 insertions(+), 682 deletions(-)

doc/langref.html.in+6-6
...@@ -958,14 +958,14 @@ const assert = std.debug.assert;...@@ -958,14 +958,14 @@ const assert = std.debug.assert;
958threadlocal var x: i32 = 1234;958threadlocal var x: i32 = 1234;
959959
960test "thread local storage" {960test "thread local storage" {
961 const thread1 = try std.Thread.spawn(testTls, {});961 const thread1 = try std.Thread.spawn(.{}, testTls, .{});
962 const thread2 = try std.Thread.spawn(testTls, {});962 const thread2 = try std.Thread.spawn(.{}, testTls, .{});
963 testTls({});963 testTls();
964 thread1.wait();964 thread1.join();
965 thread2.wait();965 thread2.join();
966}966}
967967
968fn testTls(_: void) void {968fn testTls() void {
969 assert(x == 1234);969 assert(x == 1234);
970 x += 1;970 x += 1;
971 assert(x == 1235);971 assert(x == 1235);
lib/std/Thread.zig+662-444
...@@ -8,7 +8,11 @@...@@ -8,7 +8,11 @@
8//! primitives that operate on kernel threads. For concurrency primitives that support8//! primitives that operate on kernel threads. For concurrency primitives that support
9//! both evented I/O and async I/O, see the respective names in the top level std namespace.9//! both evented I/O and async I/O, see the respective names in the top level std namespace.
1010
11data: Data,11const std = @import("std.zig");
12const os = std.os;
13const assert = std.debug.assert;
14const target = std.Target.current;
15const Atomic = std.atomic.Atomic;
1216
13pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig");17pub const AutoResetEvent = @import("Thread/AutoResetEvent.zig");
14pub const Futex = @import("Thread/Futex.zig");18pub const Futex = @import("Thread/Futex.zig");
...@@ -18,117 +22,51 @@ pub const Mutex = @import("Thread/Mutex.zig");...@@ -18,117 +22,51 @@ pub const Mutex = @import("Thread/Mutex.zig");
18pub const Semaphore = @import("Thread/Semaphore.zig");22pub const Semaphore = @import("Thread/Semaphore.zig");
19pub const Condition = @import("Thread/Condition.zig");23pub const Condition = @import("Thread/Condition.zig");
2024
21pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc;25pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");
2226
23const Thread = @This();27pub const use_pthreads = target.os.tag != .windows and std.builtin.link_libc;
24const std = @import("std.zig");
25const builtin = std.builtin;
26const os = std.os;
27const mem = std.mem;
28const windows = std.os.windows;
29const c = std.c;
30const assert = std.debug.assert;
3128
32const bad_startfn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";29const Thread = @This();
30const Impl = if (target.os.tag == .windows)
31 WindowsThreadImpl
32else if (use_pthreads)
33 PosixThreadImpl
34else if (target.os.tag == .linux)
35 LinuxThreadImpl
36else
37 UnsupportedImpl;
3338
34/// Represents a kernel thread handle.39impl: Impl,
35/// May be an integer or a pointer depending on the platform.
36/// On Linux and POSIX, this is the same as Id.
37pub const Handle = if (use_pthreads)
38 c.pthread_t
39else switch (std.Target.current.os.tag) {
40 .linux => i32,
41 .windows => windows.HANDLE,
42 else => void,
43};
4440
45/// Represents a unique ID per thread.41/// Represents a unique ID per thread.
46/// May be an integer or pointer depending on the platform.42pub const Id = u64;
47/// On Linux and POSIX, this is the same as Handle.
48pub const Id = switch (std.Target.current.os.tag) {
49 .windows => windows.DWORD,
50 else => Handle,
51};
5243
53pub const Data = if (use_pthreads)44/// Returns the platform ID of the callers thread.
54 struct {45/// Attempts to use thread locals and avoid syscalls when possible.
55 handle: Thread.Handle,
56 memory: []u8,
57 }
58else switch (std.Target.current.os.tag) {
59 .linux => struct {
60 handle: Thread.Handle,
61 memory: []align(mem.page_size) u8,
62 },
63 .windows => struct {
64 handle: Thread.Handle,
65 alloc_start: *c_void,
66 heap_handle: windows.HANDLE,
67 },
68 else => struct {},
69};
70
71pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");
72
73/// Returns the ID of the calling thread.
74/// Makes a syscall every time the function is called.
75/// On Linux and POSIX, this Id is the same as a Handle.
76pub fn getCurrentId() Id {46pub fn getCurrentId() Id {
77 if (use_pthreads) {47 return Impl.getCurrentId();
78 return c.pthread_self();
79 } else return switch (std.Target.current.os.tag) {
80 .linux => os.linux.gettid(),
81 .windows => windows.kernel32.GetCurrentThreadId(),
82 else => @compileError("Unsupported OS"),
83 };
84}48}
8549
86/// Returns the handle of this thread.50pub const CpuCountError = error{
87/// On Linux and POSIX, this is the same as Id.51 PermissionDenied,
88/// On Linux, it is possible that the thread spawned with `spawn`52 SystemResources,
89/// finishes executing entirely before the clone syscall completes. In this53 Unexpected,
90/// case, this function will return 0 rather than the no-longer-existing thread's54};
91/// pid.
92pub fn handle(self: Thread) Handle {
93 return self.data.handle;
94}
9555
96pub fn wait(self: *Thread) void {56/// Returns the platforms view on the number of logical CPU cores available.
97 if (use_pthreads) {57pub fn getCpuCount() CpuCountError!usize {
98 const err = c.pthread_join(self.data.handle, null);58 return Impl.getCpuCount();
99 switch (err) {
100 0 => {},
101 os.EINVAL => unreachable,
102 os.ESRCH => unreachable,
103 os.EDEADLK => unreachable,
104 else => unreachable,
105 }
106 std.heap.c_allocator.free(self.data.memory);
107 std.heap.c_allocator.destroy(self);
108 } else switch (std.Target.current.os.tag) {
109 .linux => {
110 while (true) {
111 const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
112 if (pid_value == 0) break;
113 const rc = os.linux.futex_wait(&self.data.handle, os.linux.FUTEX_WAIT, pid_value, null);
114 switch (os.linux.getErrno(rc)) {
115 0 => continue,
116 os.EINTR => continue,
117 os.EAGAIN => continue,
118 else => unreachable,
119 }
120 }
121 os.munmap(self.data.memory);
122 },
123 .windows => {
124 windows.WaitForSingleObjectEx(self.data.handle, windows.INFINITE, false) catch unreachable;
125 windows.CloseHandle(self.data.handle);
126 windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start);
127 },
128 else => @compileError("Unsupported OS"),
129 }
130}59}
13160
61/// Configuration options for hints on how to spawn threads.
62pub const SpawnConfig = struct {
63 // TODO compile-time call graph analysis to determine stack upper bound
64 // https://github.com/ziglang/zig/issues/157
65
66 /// Size in bytes of the Thread's stack
67 stack_size: usize = 16 * 1024 * 1024,
68};
69
132pub const SpawnError = error{70pub const SpawnError = error{
133 /// A system-imposed limit on the number of threads was encountered.71 /// A system-imposed limit on the number of threads was encountered.
134 /// There are a number of limits that may trigger this error:72 /// There are a number of limits that may trigger this error:
...@@ -159,248 +97,552 @@ pub const SpawnError = error{...@@ -159,248 +97,552 @@ pub const SpawnError = error{
159 Unexpected,97 Unexpected,
160};98};
16199
162// Given `T`, the type of the thread startFn, extract the expected type for the100/// Spawns a new thread which executes `function` using `args` and returns a handle the spawned thread.
163// context parameter.101/// `config` can be used as hints to the platform for now to spawn and execute the `function`.
164fn SpawnContextType(comptime T: type) type {102/// The caller must eventually either call `join()` to wait for the thread to finish and free its resources
165 const TI = @typeInfo(T);103/// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion`.
166 if (TI != .Fn)104pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread {
167 @compileError("expected function type, found " ++ @typeName(T));105 if (std.builtin.single_threaded) {
106 @compileError("Cannot spawn thread when building in single-threaded mode");
107 }
108
109 const impl = try Impl.spawn(config, function, args);
110 return Thread{ .impl = impl };
111}
168112
169 if (TI.Fn.args.len != 1)113/// Represents a kernel thread handle.
170 @compileError("expected function with single argument, found " ++ @typeName(T));114/// May be an integer or a pointer depending on the platform.
115pub const Handle = Impl.ThreadHandle;
171116
172 return TI.Fn.args[0].arg_type orelse117/// Retrns the handle of this thread
173 @compileError("cannot use a generic function as thread startFn");118pub fn getHandle(self: Thread) Handle {
119 return self.impl.getHandle();
174}120}
175121
176/// Spawns a new thread executing startFn, returning an handle for it.122/// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion.
177/// Caller must call wait on the returned thread.123/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
178/// The `startFn` function must take a single argument of type T and return a124pub fn detach(self: Thread) void {
179/// value of type u8, noreturn, void or !void.125 return self.impl.detach();
180/// The `context` parameter is of type T and is passed to the spawned thread.126}
181pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startFn))) SpawnError!*Thread {
182 if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
183 // TODO compile-time call graph analysis to determine stack upper bound
184 // https://github.com/ziglang/zig/issues/157
185 const default_stack_size = 16 * 1024 * 1024;
186127
187 const Context = @TypeOf(context);128/// Waits for the thread to complete, then deallocates any resources created on `spawn()`.
129/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
130pub fn join(self: Thread) void {
131 return self.impl.join();
132}
188133
189 if (std.Target.current.os.tag == .windows) {134/// State to synchronize detachment of spawner thread to spawned thread
190 const WinThread = struct {135const Completion = Atomic(enum(u8) {
191 const OuterContext = struct {136 running,
192 thread: Thread,137 detached,
193 inner: Context,138 completed,
194 };139});
195 fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD {140
196 const arg = if (@sizeOf(Context) == 0) undefined //141/// Used by the Thread implementations to call the spawned function with the arguments.
197 else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;142fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
198143 WindowsThreadImpl => std.os.windows.DWORD,
199 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {144 LinuxThreadImpl => u8,
200 .NoReturn => {145 PosixThreadImpl => ?*c_void,
201 startFn(arg);146 else => unreachable,
202 },147} {
203 .Void => {148 const default_value = if (Impl == PosixThreadImpl) null else 0;
204 startFn(arg);149 const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'";
205 return 0;150
206 },151 switch (@typeInfo(@typeInfo(@TypeOf(f)).Fn.return_type.?)) {
207 .Int => |info| {152 .NoReturn => {
208 if (info.bits != 8) {153 @call(.{}, f, args);
209 @compileError(bad_startfn_ret);154 },
210 }155 .Void => {
211 return startFn(arg);156 @call(.{}, f, args);
212 },157 return default_value;
213 .ErrorUnion => |info| {158 },
214 if (info.payload != void) {159 .Int => |info| {
215 @compileError(bad_startfn_ret);160 if (info.bits != 8) {
216 }161 @compileError(bad_fn_ret);
217 startFn(arg) catch |err| {162 }
218 std.debug.warn("error: {s}\n", .{@errorName(err)});163
219 if (@errorReturnTrace()) |trace| {164 const status = @call(.{}, f, args);
220 std.debug.dumpStackTrace(trace.*);165 if (Impl != PosixThreadImpl) {
221 }166 return status;
222 };167 }
223 return 0;168
224 },169 // pthreads don't support exit status, ignore value
225 else => @compileError(bad_startfn_ret),170 _ = status;
171 return default_value;
172 },
173 .ErrorUnion => |info| {
174 if (info.payload != void) {
175 @compileError(bad_fn_ret);
176 }
177
178 @call(.{}, f, args) catch |err| {
179 std.debug.warn("error: {s}\n", .{@errorName(err)});
180 if (@errorReturnTrace()) |trace| {
181 std.debug.dumpStackTrace(trace.*);
226 }182 }
183 };
184
185 return default_value;
186 },
187 else => {
188 @compileError(bad_fn_ret);
189 },
190 }
191}
192
193/// We can't compile error in the `Impl` switch statement as its eagerly evaluated.
194/// So instead, we compile-error on the methods themselves for platforms which don't support threads.
195const UnsupportedImpl = struct {
196 pub const ThreadHandle = void;
197
198 fn getCurrentId() u64 {
199 return unsupported({});
200 }
201
202 fn getCpuCount() !usize {
203 return unsupported({});
204 }
205
206 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
207 return unsupported(.{ config, f, args });
208 }
209
210 fn getHandle(self: Impl) ThreadHandle {
211 return unsupported(self);
212 }
213
214 fn detach(self: Impl) void {
215 return unsupported(self);
216 }
217
218 fn join(self: Impl) void {
219 return unsupported(self);
220 }
221
222 fn unsupported(unusued: anytype) noreturn {
223 @compileLog("Unsupported operating system", target.os.tag);
224 _ = unusued;
225 unreachable;
226 }
227};
228
229const WindowsThreadImpl = struct {
230 const windows = os.windows;
231
232 pub const ThreadHandle = windows.HANDLE;
233
234 fn getCurrentId() u64 {
235 return windows.kernel32.GetCurrentThreadId();
236 }
237
238 fn getCpuCount() !usize {
239 // Faster than calling into GetSystemInfo(), even if amortized.
240 return windows.peb().NumberOfProcessors;
241 }
242
243 thread: *ThreadCompletion,
244
245 const ThreadCompletion = struct {
246 completion: Completion,
247 heap_ptr: windows.PVOID,
248 heap_handle: windows.HANDLE,
249 thread_handle: windows.HANDLE = undefined,
250
251 fn free(self: ThreadCompletion) void {
252 const status = windows.kernel32.HeapFree(self.heap_handle, 0, self.heap_ptr);
253 assert(status != 0);
254 }
255 };
256
257 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
258 const Args = @TypeOf(args);
259 const Instance = struct {
260 fn_args: Args,
261 thread: ThreadCompletion,
262
263 fn entryFn(raw_ptr: windows.PVOID) callconv(.C) windows.DWORD {
264 const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), raw_ptr));
265 defer switch (self.thread.completion.swap(.completed, .SeqCst)) {
266 .running => {},
267 .completed => unreachable,
268 .detached => self.thread.free(),
269 };
270 return callFn(f, self.fn_args);
227 }271 }
228 };272 };
229273
230 const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;274 const heap_handle = windows.kernel32.GetProcessHeap() orelse return error.OutOfMemory;
231 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);275 const alloc_bytes = @alignOf(Instance) + @sizeOf(Instance);
232 const bytes_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory;276 const alloc_ptr = windows.kernel32.HeapAlloc(heap_handle, 0, alloc_bytes) orelse return error.OutOfMemory;
233 errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, bytes_ptr) != 0);277 errdefer assert(windows.kernel32.HeapFree(heap_handle, 0, alloc_ptr) != 0);
234 const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];278
235 const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;279 const instance_bytes = @ptrCast([*]u8, alloc_ptr)[0..alloc_bytes];
236 outer_context.* = WinThread.OuterContext{280 const instance = std.heap.FixedBufferAllocator.init(instance_bytes).allocator.create(Instance) catch unreachable;
237 .thread = Thread{281 instance.* = .{
238 .data = Thread.Data{282 .fn_args = args,
239 .heap_handle = heap_handle,283 .thread = .{
240 .alloc_start = bytes_ptr,284 .completion = Completion.init(.running),
241 .handle = undefined,285 .heap_ptr = alloc_ptr,
242 },286 .heap_handle = heap_handle,
243 },287 },
244 .inner = context,
245 };288 };
246289
247 const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);290 // Windows appears to only support SYSTEM_INFO.dwAllocationGranularity minimum stack size.
248 outer_context.thread.data.handle = windows.kernel32.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {291 // Going lower makes it default to that specified in the executable (~1mb).
249 switch (windows.kernel32.GetLastError()) {292 // Its also fine if the limit here is incorrect as stack size is only a hint.
250 else => |err| return windows.unexpectedError(err),293 var stack_size = std.math.cast(u32, config.stack_size) catch std.math.maxInt(u32);
251 }294 stack_size = std.math.max(64 * 1024, stack_size);
295
296 instance.thread.thread_handle = windows.kernel32.CreateThread(
297 null,
298 stack_size,
299 Instance.entryFn,
300 @ptrCast(*c_void, instance),
301 0,
302 null,
303 ) orelse {
304 const errno = windows.kernel32.GetLastError();
305 return windows.unexpectedError(errno);
252 };306 };
253 return &outer_context.thread;307
308 return Impl{ .thread = &instance.thread };
254 }309 }
255310
256 const MainFuncs = struct {311 fn getHandle(self: Impl) ThreadHandle {
257 fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 {312 return self.thread.thread_handle;
258 const arg = if (@sizeOf(Context) == 0) undefined //313 }
259 else @intToPtr(*Context, ctx_addr).*;
260314
261 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {315 fn detach(self: Impl) void {
262 .NoReturn => {316 windows.CloseHandle(self.thread.thread_handle);
263 startFn(arg);317 switch (self.thread.completion.swap(.detached, .SeqCst)) {
264 },318 .running => {},
265 .Void => {319 .completed => self.thread.free(),
266 startFn(arg);320 .detached => unreachable,
267 return 0;
268 },
269 .Int => |info| {
270 if (info.bits != 8) {
271 @compileError(bad_startfn_ret);
272 }
273 return startFn(arg);
274 },
275 .ErrorUnion => |info| {
276 if (info.payload != void) {
277 @compileError(bad_startfn_ret);
278 }
279 startFn(arg) catch |err| {
280 std.debug.warn("error: {s}\n", .{@errorName(err)});
281 if (@errorReturnTrace()) |trace| {
282 std.debug.dumpStackTrace(trace.*);
283 }
284 };
285 return 0;
286 },
287 else => @compileError(bad_startfn_ret),
288 }
289 }321 }
290 fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void {322 }
291 const arg = if (@sizeOf(Context) == 0) undefined //
292 else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*;
293323
294 switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) {324 fn join(self: Impl) void {
295 .NoReturn => {325 windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
296 startFn(arg);326 windows.CloseHandle(self.thread.thread_handle);
297 },327 assert(self.thread.completion.load(.SeqCst) == .completed);
298 .Void => {328 self.thread.free();
299 startFn(arg);329 }
300 return null;330};
301 },331
302 .Int => |info| {332const PosixThreadImpl = struct {
303 if (info.bits != 8) {333 const c = std.c;
304 @compileError(bad_startfn_ret);334
305 }335 pub const ThreadHandle = c.pthread_t;
306 // pthreads don't support exit status, ignore value336
307 _ = startFn(arg);337 fn getCurrentId() Id {
308 return null;338 switch (target.os.tag) {
309 },339 .linux => {
310 .ErrorUnion => |info| {340 return LinuxThreadImpl.getCurrentId();
311 if (info.payload != void) {341 },
312 @compileError(bad_startfn_ret);342 .macos, .ios, .watchos, .tvos => {
313 }343 var thread_id: u64 = undefined;
314 startFn(arg) catch |err| {344 // Pass thread=null to get the current thread ID.
315 std.debug.warn("error: {s}\n", .{@errorName(err)});345 assert(c.pthread_threadid_np(null, &thread_id) == 0);
316 if (@errorReturnTrace()) |trace| {346 return thread_id;
317 std.debug.dumpStackTrace(trace.*);347 },
318 }348 .dragonfly => {
319 };349 return @bitCast(u32, c.lwp_gettid());
320 return null;350 },
321 },351 .netbsd => {
322 else => @compileError(bad_startfn_ret),352 return @bitCast(u32, c._lwp_self());
323 }353 },
354 .freebsd => {
355 return @bitCast(u32, c.pthread_getthreadid_np());
356 },
357 .openbsd => {
358 return @bitCast(u32, c.getthrid());
359 },
360 .haiku => {
361 return @bitCast(u32, c.find_thread(null));
362 },
363 else => {
364 return @ptrToInt(c.pthread_self());
365 },
324 }366 }
325 };367 }
368
369 fn getCpuCount() !usize {
370 switch (target.os.tag) {
371 .linux => {
372 return LinuxThreadImpl.getCpuCount();
373 },
374 .openbsd => {
375 var count: c_int = undefined;
376 var count_size: usize = @sizeOf(c_int);
377 const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE };
378 os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
379 error.NameTooLong, error.UnknownName => unreachable,
380 else => |e| return e,
381 };
382 return @intCast(usize, count);
383 },
384 .haiku => {
385 var count: u32 = undefined;
386 var system_info: os.system_info = undefined;
387 _ = os.system.get_system_info(&system_info); // always returns B_OK
388 count = system_info.cpu_count;
389 return @intCast(usize, count);
390 },
391 else => {
392 var count: c_int = undefined;
393 var count_len: usize = @sizeOf(c_int);
394 const name = if (comptime target.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
395 os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
396 error.NameTooLong, error.UnknownName => unreachable,
397 else => |e| return e,
398 };
399 return @intCast(usize, count);
400 },
401 }
402 }
403
404 handle: ThreadHandle,
405
406 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
407 const Args = @TypeOf(args);
408 const allocator = std.heap.c_allocator;
409
410 const Instance = struct {
411 fn entryFn(raw_arg: ?*c_void) callconv(.C) ?*c_void {
412 // @alignCast() below doesn't support zero-sized-types (ZST)
413 if (@sizeOf(Args) < 1) {
414 return callFn(f, @as(Args, undefined));
415 }
416
417 const args_ptr = @ptrCast(*Args, @alignCast(@alignOf(Args), raw_arg));
418 defer allocator.destroy(args_ptr);
419 return callFn(f, args_ptr.*);
420 }
421 };
422
423 const args_ptr = try allocator.create(Args);
424 args_ptr.* = args;
425 errdefer allocator.destroy(args_ptr);
326426
327 if (Thread.use_pthreads) {
328 var attr: c.pthread_attr_t = undefined;427 var attr: c.pthread_attr_t = undefined;
329 if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;428 if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;
330 defer assert(c.pthread_attr_destroy(&attr) == 0);429 defer assert(c.pthread_attr_destroy(&attr) == 0);
331430
332 const thread_obj = try std.heap.c_allocator.create(Thread);
333 errdefer std.heap.c_allocator.destroy(thread_obj);
334 if (@sizeOf(Context) > 0) {
335 thread_obj.data.memory = try std.heap.c_allocator.allocAdvanced(
336 u8,
337 @alignOf(Context),
338 @sizeOf(Context),
339 .at_least,
340 );
341 errdefer std.heap.c_allocator.free(thread_obj.data.memory);
342 mem.copy(u8, thread_obj.data.memory, mem.asBytes(&context));
343 } else {
344 thread_obj.data.memory = @as([*]u8, undefined)[0..0];
345 }
346
347 // Use the same set of parameters used by the libc-less impl.431 // Use the same set of parameters used by the libc-less impl.
348 assert(c.pthread_attr_setstacksize(&attr, default_stack_size) == 0);432 const stack_size = std.math.max(config.stack_size, 16 * 1024);
349 assert(c.pthread_attr_setguardsize(&attr, mem.page_size) == 0);433 assert(c.pthread_attr_setstacksize(&attr, stack_size) == 0);
434 assert(c.pthread_attr_setguardsize(&attr, std.mem.page_size) == 0);
350435
351 const err = c.pthread_create(436 var handle: c.pthread_t = undefined;
352 &thread_obj.data.handle,437 switch (c.pthread_create(
438 &handle,
353 &attr,439 &attr,
354 MainFuncs.posixThreadMain,440 Instance.entryFn,
355 thread_obj.data.memory.ptr,441 if (@sizeOf(Args) > 1) @ptrCast(*c_void, args_ptr) else undefined,
356 );442 )) {
357 switch (err) {443 0 => return Impl{ .handle = handle },
358 0 => return thread_obj,
359 os.EAGAIN => return error.SystemResources,444 os.EAGAIN => return error.SystemResources,
360 os.EPERM => unreachable,445 os.EPERM => unreachable,
361 os.EINVAL => unreachable,446 os.EINVAL => unreachable,
362 else => return os.unexpectedErrno(err),447 else => |err| return os.unexpectedErrno(err),
448 }
449 }
450
451 fn getHandle(self: Impl) ThreadHandle {
452 return self.handle;
453 }
454
455 fn detach(self: Impl) void {
456 switch (c.pthread_detach(self.handle)) {
457 0 => {},
458 os.EINVAL => unreachable, // thread handle is not joinable
459 os.ESRCH => unreachable, // thread handle is invalid
460 else => unreachable,
461 }
462 }
463
464 fn join(self: Impl) void {
465 switch (c.pthread_join(self.handle, null)) {
466 0 => {},
467 os.EINVAL => unreachable, // thread handle is not joinable (or another thread is already joining in)
468 os.ESRCH => unreachable, // thread handle is invalid
469 os.EDEADLK => unreachable, // two threads tried to join each other
470 else => unreachable,
363 }471 }
472 }
473};
474
475const LinuxThreadImpl = struct {
476 const linux = os.linux;
364477
365 return thread_obj;478 pub const ThreadHandle = i32;
479
480 threadlocal var tls_thread_id: ?Id = null;
481
482 fn getCurrentId() Id {
483 return tls_thread_id orelse {
484 const tid = @bitCast(u32, linux.gettid());
485 tls_thread_id = tid;
486 return tid;
487 };
366 }488 }
367489
368 var guard_end_offset: usize = undefined;490 fn getCpuCount() !usize {
369 var stack_end_offset: usize = undefined;491 const cpu_set = try os.sched_getaffinity(0);
370 var thread_start_offset: usize = undefined;492 // TODO: should not need this usize cast
371 var context_start_offset: usize = undefined;493 return @as(usize, os.CPU_COUNT(cpu_set));
372 var tls_start_offset: usize = undefined;494 }
373 const mmap_len = blk: {495
374 var l: usize = mem.page_size;496 thread: *ThreadCompletion,
375 // Allocate a guard page right after the end of the stack region497
376 guard_end_offset = l;498 const ThreadCompletion = struct {
377 // The stack itself, which grows downwards.499 completion: Completion = Completion.init(.running),
378 l = mem.alignForward(l + default_stack_size, mem.page_size);500 child_tid: Atomic(i32) = Atomic(i32).init(1),
379 stack_end_offset = l;501 parent_tid: i32 = undefined,
380 // Above the stack, so that it can be in the same mmap call, put the Thread object.502 mapped: []align(std.mem.page_size) u8,
381 l = mem.alignForward(l, @alignOf(Thread));503
382 thread_start_offset = l;504 /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`).
383 l += @sizeOf(Thread);505 /// Ported over from musl libc's pthread detached implementation:
384 // Next, the Context object.506 /// https://github.com/ifduyue/musl/search?q=__unmapself
385 if (@sizeOf(Context) != 0) {507 fn freeAndExit(self: *ThreadCompletion) noreturn {
386 l = mem.alignForward(l, @alignOf(Context));508 const unmap_and_exit: []const u8 = switch (target.cpu.arch) {
387 context_start_offset = l;509 .i386 => (
388 l += @sizeOf(Context);510 \\ movl $91, %%eax
511 \\ movl %[ptr], %%ebx
512 \\ movl %[len], %%ecx
513 \\ int $128
514 \\ movl $1, %%eax
515 \\ movl $0, %%ebx
516 \\ int $128
517 ),
518 .x86_64 => (
519 \\ movq $11, %%rax
520 \\ movq %[ptr], %%rbx
521 \\ movq %[len], %%rcx
522 \\ syscall
523 \\ movq $60, %%rax
524 \\ movq $1, %%rdi
525 \\ syscall
526 ),
527 .arm, .armeb, .thumb, .thumbeb => (
528 \\ mov r7, #91
529 \\ mov r0, %[ptr]
530 \\ mov r1, %[len]
531 \\ svc 0
532 \\ mov r7, #1
533 \\ mov r0, #0
534 \\ svc 0
535 ),
536 .aarch64, .aarch64_be, .aarch64_32 => (
537 \\ mov x8, #215
538 \\ mov x0, %[ptr]
539 \\ mov x1, %[len]
540 \\ svc 0
541 \\ mov x8, #93
542 \\ mov x0, #0
543 \\ svc 0
544 ),
545 .mips, .mipsel => (
546 \\ move $sp, $25
547 \\ li $2, 4091
548 \\ move $4, %[ptr]
549 \\ move $5, %[len]
550 \\ syscall
551 \\ li $2, 4001
552 \\ li $4, 0
553 \\ syscall
554 ),
555 .mips64, .mips64el => (
556 \\ li $2, 4091
557 \\ move $4, %[ptr]
558 \\ move $5, %[len]
559 \\ syscall
560 \\ li $2, 4001
561 \\ li $4, 0
562 \\ syscall
563 ),
564 .powerpc, .powerpcle, .powerpc64, .powerpc64le => (
565 \\ li 0, 91
566 \\ mr %[ptr], 3
567 \\ mr %[len], 4
568 \\ sc
569 \\ li 0, 1
570 \\ li 3, 0
571 \\ sc
572 \\ blr
573 ),
574 .riscv64 => (
575 \\ li a7, 215
576 \\ mv a0, %[ptr]
577 \\ mv a1, %[len]
578 \\ ecall
579 \\ li a7, 93
580 \\ mv a0, zero
581 \\ ecall
582 ),
583 else => |cpu_arch| {
584 @compileLog("Unsupported linux arch ", cpu_arch);
585 },
586 };
587
588 asm volatile (unmap_and_exit
589 :
590 : [ptr] "r" (@ptrToInt(self.mapped.ptr)),
591 [len] "r" (self.mapped.len)
592 : "memory"
593 );
594
595 unreachable;
389 }596 }
390 // Finally, the Thread Local Storage, if any.
391 l = mem.alignForward(l, os.linux.tls.tls_image.alloc_align);
392 tls_start_offset = l;
393 l += os.linux.tls.tls_image.alloc_size;
394 // Round the size to the page size.
395 break :blk mem.alignForward(l, mem.page_size);
396 };597 };
397598
398 const mmap_slice = mem: {599 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
399 // Map the whole stack with no rw permissions to avoid600 const Args = @TypeOf(args);
400 // committing the whole region right away601 const Instance = struct {
401 const mmap_slice = os.mmap(602 fn_args: Args,
603 thread: ThreadCompletion,
604
605 fn entryFn(raw_arg: usize) callconv(.C) u8 {
606 const self = @intToPtr(*@This(), raw_arg);
607 defer switch (self.thread.completion.swap(.completed, .SeqCst)) {
608 .running => {},
609 .completed => unreachable,
610 .detached => self.thread.freeAndExit(),
611 };
612 return callFn(f, self.fn_args);
613 }
614 };
615
616 var guard_offset: usize = undefined;
617 var stack_offset: usize = undefined;
618 var tls_offset: usize = undefined;
619 var instance_offset: usize = undefined;
620
621 const map_bytes = blk: {
622 var bytes: usize = std.mem.page_size;
623 guard_offset = bytes;
624
625 bytes += std.math.max(std.mem.page_size, config.stack_size);
626 bytes = std.mem.alignForward(bytes, std.mem.page_size);
627 stack_offset = bytes;
628
629 bytes = std.mem.alignForward(bytes, linux.tls.tls_image.alloc_align);
630 tls_offset = bytes;
631 bytes += linux.tls.tls_image.alloc_size;
632
633 bytes = std.mem.alignForward(bytes, @alignOf(Instance));
634 instance_offset = bytes;
635 bytes += @sizeOf(Instance);
636
637 bytes = std.mem.alignForward(bytes, std.mem.page_size);
638 break :blk bytes;
639 };
640
641 // map all memory needed without read/write permissions
642 // to avoid committing the whole region right away
643 const mapped = os.mmap(
402 null,644 null,
403 mmap_len,645 map_bytes,
404 os.PROT_NONE,646 os.PROT_NONE,
405 os.MAP_PRIVATE | os.MAP_ANONYMOUS,647 os.MAP_PRIVATE | os.MAP_ANONYMOUS,
406 -1,648 -1,
...@@ -411,73 +653,57 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF...@@ -411,73 +653,57 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF
411 error.PermissionDenied => unreachable,653 error.PermissionDenied => unreachable,
412 else => |e| return e,654 else => |e| return e,
413 };655 };
414 errdefer os.munmap(mmap_slice);656 assert(mapped.len >= map_bytes);
657 errdefer os.munmap(mapped);
415658
416 // Map everything but the guard page as rw659 // map everything but the guard page as read/write
417 os.mprotect(660 os.mprotect(
418 mmap_slice[guard_end_offset..],661 mapped[guard_offset..],
419 os.PROT_READ | os.PROT_WRITE,662 os.PROT_READ | os.PROT_WRITE,
420 ) catch |err| switch (err) {663 ) catch |err| switch (err) {
421 error.AccessDenied => unreachable,664 error.AccessDenied => unreachable,
422 else => |e| return e,665 else => |e| return e,
423 };666 };
424667
425 break :mem mmap_slice;668 // Prepare the TLS segment and prepare a user_desc struct when needed on i386
426 };669 var tls_ptr = os.linux.tls.prepareTLS(mapped[tls_offset..]);
427670 var user_desc: if (target.cpu.arch == .i386) os.linux.user_desc else void = undefined;
428 const mmap_addr = @ptrToInt(mmap_slice.ptr);671 if (target.cpu.arch == .i386) {
429672 defer tls_ptr = @ptrToInt(&user_desc);
430 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));673 user_desc = .{
431 thread_ptr.data.memory = mmap_slice;674 .entry_number = os.linux.tls.tls_image.gdt_entry_number,
675 .base_addr = tls_ptr,
676 .limit = 0xfffff,
677 .seg_32bit = 1,
678 .contents = 0, // Data
679 .read_exec_only = 0,
680 .limit_in_pages = 1,
681 .seg_not_present = 0,
682 .useable = 1,
683 };
684 }
432685
433 var arg: usize = undefined;686 const instance = @ptrCast(*Instance, @alignCast(@alignOf(Instance), &mapped[instance_offset]));
434 if (@sizeOf(Context) != 0) {687 instance.* = .{
435 arg = mmap_addr + context_start_offset;688 .fn_args = args,
436 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));689 .thread = .{ .mapped = mapped },
437 context_ptr.* = context;690 };
438 }
439691
440 if (std.Target.current.os.tag == .linux) {692 const flags: u32 = os.CLONE_THREAD | os.CLONE_DETACHED |
441 const flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |693 os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES |
442 os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM |
443 os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |694 os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID |
444 os.CLONE_DETACHED | os.CLONE_SETTLS;695 os.CLONE_SIGHAND | os.CLONE_SYSVSEM | os.CLONE_SETTLS;
445 // This structure is only needed when targeting i386
446 var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined;
447
448 const tls_area = mmap_slice[tls_start_offset..];
449 const tp_value = os.linux.tls.prepareTLS(tls_area);
450
451 const newtls = blk: {
452 if (std.Target.current.cpu.arch == .i386) {
453 user_desc = os.linux.user_desc{
454 .entry_number = os.linux.tls.tls_image.gdt_entry_number,
455 .base_addr = tp_value,
456 .limit = 0xfffff,
457 .seg_32bit = 1,
458 .contents = 0, // Data
459 .read_exec_only = 0,
460 .limit_in_pages = 1,
461 .seg_not_present = 0,
462 .useable = 1,
463 };
464 break :blk @ptrToInt(&user_desc);
465 } else {
466 break :blk tp_value;
467 }
468 };
469696
470 const rc = os.linux.clone(697 switch (linux.getErrno(linux.clone(
471 MainFuncs.linuxThreadMain,698 Instance.entryFn,
472 mmap_addr + stack_end_offset,699 @ptrToInt(&mapped[stack_offset]),
473 flags,700 flags,
474 arg,701 @ptrToInt(instance),
475 &thread_ptr.data.handle,702 &instance.thread.parent_tid,
476 newtls,703 tls_ptr,
477 &thread_ptr.data.handle,704 &instance.thread.child_tid.value,
478 );705 ))) {
479 switch (os.errno(rc)) {706 0 => return Impl{ .thread = &instance.thread },
480 0 => return thread_ptr,
481 os.EAGAIN => return error.ThreadQuotaExceeded,707 os.EAGAIN => return error.ThreadQuotaExceeded,
482 os.EINVAL => unreachable,708 os.EINVAL => unreachable,
483 os.ENOMEM => return error.SystemResources,709 os.ENOMEM => return error.SystemResources,
...@@ -486,100 +712,92 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF...@@ -486,100 +712,92 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF
486 os.EUSERS => unreachable,712 os.EUSERS => unreachable,
487 else => |err| return os.unexpectedErrno(err),713 else => |err| return os.unexpectedErrno(err),
488 }714 }
489 } else {
490 @compileError("Unsupported OS");
491 }715 }
492}
493716
494pub const CpuCountError = error{717 fn getHandle(self: Impl) ThreadHandle {
495 PermissionDenied,718 return self.thread.parent_tid;
496 SystemResources,719 }
497 Unexpected,
498};
499720
500pub fn cpuCount() CpuCountError!usize {721 fn detach(self: Impl) void {
501 switch (std.Target.current.os.tag) {722 switch (self.thread.completion.swap(.detached, .SeqCst)) {
502 .linux => {723 .running => {},
503 const cpu_set = try os.sched_getaffinity(0);724 .completed => self.join(),
504 return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast725 .detached => unreachable,
505 },726 }
506 .windows => {
507 return os.windows.peb().NumberOfProcessors;
508 },
509 .openbsd => {
510 var count: c_int = undefined;
511 var count_size: usize = @sizeOf(c_int);
512 const mib = [_]c_int{ os.CTL_HW, os.HW_NCPUONLINE };
513 os.sysctl(&mib, &count, &count_size, null, 0) catch |err| switch (err) {
514 error.NameTooLong, error.UnknownName => unreachable,
515 else => |e| return e,
516 };
517 return @intCast(usize, count);
518 },
519 .haiku => {
520 var count: u32 = undefined;
521 // var system_info: os.system_info = undefined;
522 // const rc = os.system.get_system_info(&system_info);
523 count = system_info.cpu_count;
524 return @intCast(usize, count);
525 },
526 else => {
527 var count: c_int = undefined;
528 var count_len: usize = @sizeOf(c_int);
529 const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu";
530 os.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) {
531 error.NameTooLong, error.UnknownName => unreachable,
532 else => |e| return e,
533 };
534 return @intCast(usize, count);
535 },
536 }727 }
537}
538728
539pub fn getCurrentThreadId() u64 {729 fn join(self: Impl) void {
540 switch (std.Target.current.os.tag) {730 defer os.munmap(self.thread.mapped);
541 .linux => {731
542 // Use the syscall directly as musl doesn't provide a wrapper.732 var spin: u8 = 10;
543 return @bitCast(u32, os.linux.gettid());733 while (true) {
544 },734 const tid = self.thread.child_tid.load(.SeqCst);
545 .windows => {735 if (tid == 0) {
546 return os.windows.kernel32.GetCurrentThreadId();736 break;
547 },737 }
548 .macos, .ios, .watchos, .tvos => {738
549 var thread_id: u64 = undefined;739 if (spin > 0) {
550 // Pass thread=null to get the current thread ID.740 spin -= 1;
551 assert(c.pthread_threadid_np(null, &thread_id) == 0);741 std.atomic.spinLoopHint();
552 return thread_id;742 continue;
553 },743 }
554 .dragonfly => {744
555 return @bitCast(u32, c.lwp_gettid());745 switch (linux.getErrno(linux.futex_wait(
556 },746 &self.thread.child_tid.value,
557 .netbsd => {747 linux.FUTEX_WAIT,
558 return @bitCast(u32, c._lwp_self());748 tid,
559 },749 null,
560 .freebsd => {750 ))) {
561 return @bitCast(u32, c.pthread_getthreadid_np());751 0 => continue,
562 },752 os.EINTR => continue,
563 .openbsd => {753 os.EAGAIN => continue,
564 return @bitCast(u32, c.getthrid());754 else => unreachable,
565 },755 }
566 .haiku => {756 }
567 return @bitCast(u32, c.find_thread(null));
568 },
569 else => {
570 @compileError("getCurrentThreadId not implemented for this platform");
571 },
572 }757 }
573}758};
574759
575test "std.Thread" {760test "std.Thread" {
576 if (!builtin.single_threaded) {761 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
577 _ = AutoResetEvent;762 _ = AutoResetEvent;
578 _ = Futex;763 _ = Futex;
579 _ = ResetEvent;764 _ = ResetEvent;
580 _ = StaticResetEvent;765 _ = StaticResetEvent;
581 _ = Mutex;766 _ = Mutex;
582 _ = Semaphore;767 _ = Semaphore;
583 _ = Condition;768 _ = Condition;
584 }769}
770
771fn testIncrementNotify(value: *usize, event: *ResetEvent) void {
772 value.* += 1;
773 event.set();
774}
775
776test "Thread.join" {
777 if (std.builtin.single_threaded) return error.SkipZigTest;
778
779 var value: usize = 0;
780 var event: ResetEvent = undefined;
781 try event.init();
782 defer event.deinit();
783
784 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
785 thread.join();
786
787 try std.testing.expectEqual(value, 1);
788}
789
790test "Thread.detach" {
791 if (std.builtin.single_threaded) return error.SkipZigTest;
792
793 var value: usize = 0;
794 var event: ResetEvent = undefined;
795 try event.init();
796 defer event.deinit();
797
798 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
799 thread.detach();
800
801 event.wait();
802 try std.testing.expectEqual(value, 1);
585}803}
lib/std/Thread/AutoResetEvent.zig+4-4
...@@ -220,9 +220,9 @@ test "basic usage" {...@@ -220,9 +220,9 @@ test "basic usage" {
220 };220 };
221221
222 var context = Context{};222 var context = Context{};
223 const send_thread = try std.Thread.spawn(Context.sender, &context);223 const send_thread = try std.Thread.spawn(.{}, Context.sender, .{&context});
224 const recv_thread = try std.Thread.spawn(Context.receiver, &context);224 const recv_thread = try std.Thread.spawn(.{}, Context.receiver, .{&context});
225225
226 send_thread.wait();226 send_thread.join();
227 recv_thread.wait();227 recv_thread.join();
228}228}
lib/std/Thread/Futex.zig+116-124
...@@ -64,9 +64,8 @@ pub fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut}...@@ -64,9 +64,8 @@ pub fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut}
64/// Unblocks at most `num_waiters` callers blocked in a `wait()` call on `ptr`.64/// Unblocks at most `num_waiters` callers blocked in a `wait()` call on `ptr`.
65/// `num_waiters` of 1 unblocks at most one `wait(ptr, ...)` and `maxInt(u32)` unblocks effectively all `wait(ptr, ...)`.65/// `num_waiters` of 1 unblocks at most one `wait(ptr, ...)` and `maxInt(u32)` unblocks effectively all `wait(ptr, ...)`.
66pub fn wake(ptr: *const Atomic(u32), num_waiters: u32) void {66pub fn wake(ptr: *const Atomic(u32), num_waiters: u32) void {
67 if (num_waiters == 0 or single_threaded) {67 if (single_threaded) return;
68 return;68 if (num_waiters == 0) return;
69 }
7069
71 return OsFutex.wake(ptr, num_waiters);70 return OsFutex.wake(ptr, num_waiters);
72}71}
...@@ -80,7 +79,23 @@ else if (target.isDarwin())...@@ -80,7 +79,23 @@ else if (target.isDarwin())
80else if (std.builtin.link_libc)79else if (std.builtin.link_libc)
81 PosixFutex80 PosixFutex
82else81else
83 @compileError("Operating System unsupported");82 UnsupportedFutex;
83
84const UnsupportedFutex = struct {
85 fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{TimedOut}!void {
86 return unsupported(.{ ptr, expect, timeout });
87 }
88
89 fn wake(ptr: *const Atomic(u32), num_waiters: u32) void {
90 return unsupported(.{ ptr, num_waiters });
91 }
92
93 fn unsupported(unused: anytype) noreturn {
94 @compileLog("Unsupported operating system", target.os.tag);
95 _ = unused;
96 unreachable;
97 }
98};
8499
85const WindowsFutex = struct {100const WindowsFutex = struct {
86 const windows = std.os.windows;101 const windows = std.os.windows;
...@@ -391,75 +406,73 @@ test "Futex - wait/wake" {...@@ -391,75 +406,73 @@ test "Futex - wait/wake" {
391}406}
392407
393test "Futex - Signal" {408test "Futex - Signal" {
394 if (!single_threaded) {409 if (single_threaded) {
395 return;410 return error.SkipZigTest;
396 }411 }
397412
398 try (struct {413 const Paddle = struct {
399 value: Atomic(u32) = Atomic(u32).init(0),414 value: Atomic(u32) = Atomic(u32).init(0),
415 current: u32 = 0,
400416
401 const Self = @This();417 fn run(self: *@This(), hit_to: *@This()) !void {
418 var iterations: usize = 4;
419 while (iterations > 0) : (iterations -= 1) {
420 var value: u32 = undefined;
421 while (true) {
422 value = self.value.load(.Acquire);
423 if (value != self.current) break;
424 Futex.wait(&self.value, self.current, null) catch unreachable;
425 }
402426
403 fn send(self: *Self, value: u32) void {427 try testing.expectEqual(value, self.current + 1);
404 self.value.store(value, .Release);428 self.current = value;
405 Futex.wake(&self.value, 1);
406 }
407429
408 fn recv(self: *Self, expected: u32) void {430 _ = hit_to.value.fetchAdd(1, .Release);
409 while (true) {431 Futex.wake(&hit_to.value, 1);
410 const value = self.value.load(.Acquire);
411 if (value == expected) break;
412 Futex.wait(&self.value, value, null) catch unreachable;
413 }432 }
414 }433 }
434 };
415435
416 const Thread = struct {436 var ping = Paddle{};
417 tx: *Self,437 var pong = Paddle{};
418 rx: *Self,
419
420 const start_value = 1;
421
422 fn run(self: Thread) void {
423 var iterations: u32 = start_value;
424 while (iterations < 10) : (iterations += 1) {
425 self.rx.recv(iterations);
426 self.tx.send(iterations);
427 }
428 }
429 };
430
431 fn run() !void {
432 var ping = Self{};
433 var pong = Self{};
434438
435 const t1 = try std.Thread.spawn(Thread.run, .{ .rx = &ping, .tx = &pong });439 const t1 = try std.Thread.spawn(.{}, Paddle.run, .{ &ping, &pong });
436 defer t1.wait();440 defer t1.join();
437441
438 const t2 = try std.Thread.spawn(Thread.run, .{ .rx = &pong, .tx = &ping });442 const t2 = try std.Thread.spawn(.{}, Paddle.run, .{ &pong, &ping });
439 defer t2.wait();443 defer t2.join();
440444
441 ping.send(Thread.start_value);445 _ = ping.value.fetchAdd(1, .Release);
442 }446 Futex.wake(&ping.value, 1);
443 }).run();
444}447}
445448
446test "Futex - Broadcast" {449test "Futex - Broadcast" {
447 if (!single_threaded) {450 if (single_threaded) {
448 return;451 return error.SkipZigTest;
449 }452 }
450453
451 try (struct {454 const Context = struct {
452 threads: [10]*std.Thread = undefined,455 threads: [4]std.Thread = undefined,
453 broadcast: Atomic(u32) = Atomic(u32).init(0),456 broadcast: Atomic(u32) = Atomic(u32).init(0),
454 notified: Atomic(usize) = Atomic(usize).init(0),457 notified: Atomic(usize) = Atomic(usize).init(0),
455458
456 const Self = @This();
457
458 const BROADCAST_EMPTY = 0;459 const BROADCAST_EMPTY = 0;
459 const BROADCAST_SENT = 1;460 const BROADCAST_SENT = 1;
460 const BROADCAST_RECEIVED = 2;461 const BROADCAST_RECEIVED = 2;
461462
462 fn runReceiver(self: *Self) void {463 fn runSender(self: *@This()) !void {
464 self.broadcast.store(BROADCAST_SENT, .Monotonic);
465 Futex.wake(&self.broadcast, @intCast(u32, self.threads.len));
466
467 while (true) {
468 const broadcast = self.broadcast.load(.Acquire);
469 if (broadcast == BROADCAST_RECEIVED) break;
470 try testing.expectEqual(broadcast, BROADCAST_SENT);
471 Futex.wait(&self.broadcast, broadcast, null) catch unreachable;
472 }
473 }
474
475 fn runReceiver(self: *@This()) void {
463 while (true) {476 while (true) {
464 const broadcast = self.broadcast.load(.Acquire);477 const broadcast = self.broadcast.load(.Acquire);
465 if (broadcast == BROADCAST_SENT) break;478 if (broadcast == BROADCAST_SENT) break;
...@@ -473,98 +486,77 @@ test "Futex - Broadcast" {...@@ -473,98 +486,77 @@ test "Futex - Broadcast" {
473 Futex.wake(&self.broadcast, 1);486 Futex.wake(&self.broadcast, 1);
474 }487 }
475 }488 }
489 };
476490
477 fn run() !void {491 var ctx = Context{};
478 var self = Self{};492 for (ctx.threads) |*thread|
479493 thread.* = try std.Thread.spawn(.{}, Context.runReceiver, .{&ctx});
480 for (self.threads) |*thread|494 defer for (ctx.threads) |thread|
481 thread.* = try std.Thread.spawn(runReceiver, &self);495 thread.join();
482 defer for (self.threads) |thread|
483 thread.wait();
484496
485 std.time.sleep(16 * std.time.ns_per_ms);497 // Try to wait for the threads to start before running runSender().
486 self.broadcast.store(BROADCAST_SENT, .Monotonic);498 // NOTE: not actually needed for correctness.
487 Futex.wake(&self.broadcast, @intCast(u32, self.threads.len));499 std.time.sleep(16 * std.time.ns_per_ms);
500 try ctx.runSender();
488501
489 while (true) {502 const notified = ctx.notified.load(.Monotonic);
490 const broadcast = self.broadcast.load(.Acquire);503 try testing.expectEqual(notified, ctx.threads.len);
491 if (broadcast == BROADCAST_RECEIVED) break;
492 try testing.expectEqual(broadcast, BROADCAST_SENT);
493 Futex.wait(&self.broadcast, broadcast, null) catch unreachable;
494 }
495
496 const notified = self.notified.load(.Monotonic);
497 try testing.expectEqual(notified, self.threads.len);
498 }
499 }).run();
500}504}
501505
502test "Futex - Chain" {506test "Futex - Chain" {
503 if (!single_threaded) {507 if (single_threaded) {
504 return;508 return error.SkipZigTest;
505 }509 }
506510
507 try (struct {511 const Signal = struct {
508 completed: Signal = .{},512 value: Atomic(u32) = Atomic(u32).init(0),
509 threads: [10]struct {
510 thread: *std.Thread,
511 signal: Signal,
512 } = undefined,
513
514 const Signal = struct {
515 state: Atomic(u32) = Atomic(u32).init(0),
516
517 fn wait(self: *Signal) void {
518 while (true) {
519 const value = self.value.load(.Acquire);
520 if (value == 1) break;
521 assert(value == 0);
522 Futex.wait(&self.value, 0, null) catch unreachable;
523 }
524 }
525513
526 fn notify(self: *Signal) void {514 fn wait(self: *@This()) void {
527 assert(self.value.load(.Unordered) == 0);515 while (true) {
528 self.value.store(1, .Release);516 const value = self.value.load(.Acquire);
529 Futex.wake(&self.value, 1);517 if (value == 1) break;
518 assert(value == 0);
519 Futex.wait(&self.value, 0, null) catch unreachable;
530 }520 }
531 };521 }
532522
533 const Self = @This();523 fn notify(self: *@This()) void {
534 const Chain = struct {524 assert(self.value.load(.Unordered) == 0);
535 self: *Self,525 self.value.store(1, .Release);
536 index: usize,526 Futex.wake(&self.value, 1);
527 }
528 };
537529
538 fn run(chain: Chain) void {530 const Context = struct {
539 const this_signal = &chain.self.threads[chain.index].signal;531 completed: Signal = .{},
532 threads: [4]struct {
533 thread: std.Thread,
534 signal: Signal,
535 } = undefined,
540536
541 var next_signal = &chain.self.completed;537 fn run(self: *@This(), index: usize) void {
542 if (chain.index + 1 < chain.self.threads.len) {538 const this_signal = &self.threads[index].signal;
543 next_signal = &chain.self.threads[chain.index + 1].signal;
544 }
545539
546 this_signal.wait();540 var next_signal = &self.completed;
547 next_signal.notify();541 if (index + 1 < self.threads.len) {
542 next_signal = &self.threads[index + 1].signal;
548 }543 }
549 };
550544
551 fn run() !void {545 this_signal.wait();
552 var self = Self{};546 next_signal.notify();
547 }
548 };
553549
554 for (self.threads) |*entry, index| {550 var ctx = Context{};
555 entry.signal = .{};551 for (ctx.threads) |*entry, index| {
556 entry.thread = try std.Thread.spawn(Chain.run, .{552 entry.signal = .{};
557 .self = &self,553 entry.thread = try std.Thread.spawn(.{}, Context.run, .{ &ctx, index });
558 .index = index,554 }
559 });
560 }
561555
562 self.threads[0].signal.notify();556 ctx.threads[0].signal.notify();
563 self.completed.wait();557 ctx.completed.wait();
564558
565 for (self.threads) |entry| {559 for (ctx.threads) |entry| {
566 entry.thread.wait();560 entry.thread.join();
567 }561 }
568 }
569 }).run();
570}562}
lib/std/Thread/Mutex.zig+3-3
...@@ -297,12 +297,12 @@ test "basic usage" {...@@ -297,12 +297,12 @@ test "basic usage" {
297 try testing.expect(context.data == TestContext.incr_count);297 try testing.expect(context.data == TestContext.incr_count);
298 } else {298 } else {
299 const thread_count = 10;299 const thread_count = 10;
300 var threads: [thread_count]*std.Thread = undefined;300 var threads: [thread_count]std.Thread = undefined;
301 for (threads) |*t| {301 for (threads) |*t| {
302 t.* = try std.Thread.spawn(worker, &context);302 t.* = try std.Thread.spawn(.{}, worker, .{&context});
303 }303 }
304 for (threads) |t|304 for (threads) |t|
305 t.wait();305 t.join();
306306
307 try testing.expect(context.data == thread_count * TestContext.incr_count);307 try testing.expect(context.data == thread_count * TestContext.incr_count);
308 }308 }
lib/std/Thread/ResetEvent.zig+4-4
...@@ -281,8 +281,8 @@ test "basic usage" {...@@ -281,8 +281,8 @@ test "basic usage" {
281 var context: Context = undefined;281 var context: Context = undefined;
282 try context.init();282 try context.init();
283 defer context.deinit();283 defer context.deinit();
284 const receiver = try std.Thread.spawn(Context.receiver, &context);284 const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context});
285 defer receiver.wait();285 defer receiver.join();
286 try context.sender();286 try context.sender();
287287
288 if (false) {288 if (false) {
...@@ -290,8 +290,8 @@ test "basic usage" {...@@ -290,8 +290,8 @@ test "basic usage" {
290 // https://github.com/ziglang/zig/issues/7009290 // https://github.com/ziglang/zig/issues/7009
291 var timed = Context.init();291 var timed = Context.init();
292 defer timed.deinit();292 defer timed.deinit();
293 const sleeper = try std.Thread.spawn(Context.sleeper, &timed);293 const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed});
294 defer sleeper.wait();294 defer sleeper.join();
295 try timed.timedWaiter();295 try timed.timedWaiter();
296 }296 }
297}297}
lib/std/Thread/StaticResetEvent.zig+4-4
...@@ -384,8 +384,8 @@ test "basic usage" {...@@ -384,8 +384,8 @@ test "basic usage" {
384 };384 };
385385
386 var context = Context{};386 var context = Context{};
387 const receiver = try std.Thread.spawn(Context.receiver, &context);387 const receiver = try std.Thread.spawn(.{}, Context.receiver, .{&context});
388 defer receiver.wait();388 defer receiver.join();
389 try context.sender();389 try context.sender();
390390
391 if (false) {391 if (false) {
...@@ -393,8 +393,8 @@ test "basic usage" {...@@ -393,8 +393,8 @@ test "basic usage" {
393 // https://github.com/ziglang/zig/issues/7009393 // https://github.com/ziglang/zig/issues/7009
394 var timed = Context.init();394 var timed = Context.init();
395 defer timed.deinit();395 defer timed.deinit();
396 const sleeper = try std.Thread.spawn(Context.sleeper, &timed);396 const sleeper = try std.Thread.spawn(.{}, Context.sleeper, .{&timed});
397 defer sleeper.wait();397 defer sleeper.join();
398 try timed.timedWaiter();398 try timed.timedWaiter();
399 }399 }
400}400}
lib/std/atomic/queue.zig+6-6
...@@ -214,20 +214,20 @@ test "std.atomic.Queue" {...@@ -214,20 +214,20 @@ test "std.atomic.Queue" {
214 } else {214 } else {
215 try expect(context.queue.isEmpty());215 try expect(context.queue.isEmpty());
216216
217 var putters: [put_thread_count]*std.Thread = undefined;217 var putters: [put_thread_count]std.Thread = undefined;
218 for (putters) |*t| {218 for (putters) |*t| {
219 t.* = try std.Thread.spawn(startPuts, &context);219 t.* = try std.Thread.spawn(.{}, startPuts, .{&context});
220 }220 }
221 var getters: [put_thread_count]*std.Thread = undefined;221 var getters: [put_thread_count]std.Thread = undefined;
222 for (getters) |*t| {222 for (getters) |*t| {
223 t.* = try std.Thread.spawn(startGets, &context);223 t.* = try std.Thread.spawn(.{}, startGets, .{&context});
224 }224 }
225225
226 for (putters) |t|226 for (putters) |t|
227 t.wait();227 t.join();
228 @atomicStore(bool, &context.puts_done, true, .SeqCst);228 @atomicStore(bool, &context.puts_done, true, .SeqCst);
229 for (getters) |t|229 for (getters) |t|
230 t.wait();230 t.join();
231231
232 try expect(context.queue.isEmpty());232 try expect(context.queue.isEmpty());
233 }233 }
lib/std/atomic/stack.zig+6-6
...@@ -121,20 +121,20 @@ test "std.atomic.stack" {...@@ -121,20 +121,20 @@ test "std.atomic.stack" {
121 }121 }
122 }122 }
123 } else {123 } else {
124 var putters: [put_thread_count]*std.Thread = undefined;124 var putters: [put_thread_count]std.Thread = undefined;
125 for (putters) |*t| {125 for (putters) |*t| {
126 t.* = try std.Thread.spawn(startPuts, &context);126 t.* = try std.Thread.spawn(.{}, startPuts, .{&context});
127 }127 }
128 var getters: [put_thread_count]*std.Thread = undefined;128 var getters: [put_thread_count]std.Thread = undefined;
129 for (getters) |*t| {129 for (getters) |*t| {
130 t.* = try std.Thread.spawn(startGets, &context);130 t.* = try std.Thread.spawn(.{}, startGets, .{&context});
131 }131 }
132132
133 for (putters) |t|133 for (putters) |t|
134 t.wait();134 t.join();
135 @atomicStore(bool, &context.puts_done, true, .SeqCst);135 @atomicStore(bool, &context.puts_done, true, .SeqCst);
136 for (getters) |t|136 for (getters) |t|
137 t.wait();137 t.join();
138 }138 }
139139
140 if (context.put_sum != context.get_sum) {140 if (context.put_sum != context.get_sum) {
lib/std/c.zig+1
...@@ -277,6 +277,7 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us...@@ -277,6 +277,7 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us
277pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int;277pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int;
278pub extern "c" fn pthread_self() pthread_t;278pub extern "c" fn pthread_self() pthread_t;
279pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int;279pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int;
280pub extern "c" fn pthread_detach(thread: pthread_t) c_int;
280pub extern "c" fn pthread_atfork(281pub extern "c" fn pthread_atfork(
281 prepare: ?fn () callconv(.C) void,282 prepare: ?fn () callconv(.C) void,
282 parent: ?fn () callconv(.C) void,283 parent: ?fn () callconv(.C) void,
lib/std/debug.zig+2-2
...@@ -273,8 +273,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c...@@ -273,8 +273,8 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
273 if (builtin.single_threaded) {273 if (builtin.single_threaded) {
274 stderr.print("panic: ", .{}) catch os.abort();274 stderr.print("panic: ", .{}) catch os.abort();
275 } else {275 } else {
276 const current_thread_id = std.Thread.getCurrentThreadId();276 const current_thread_id = std.Thread.getCurrentId();
277 stderr.print("thread {d} panic: ", .{current_thread_id}) catch os.abort();277 stderr.print("thread {} panic: ", .{current_thread_id}) catch os.abort();
278 }278 }
279 stderr.print(format ++ "\n", args) catch os.abort();279 stderr.print(format ++ "\n", args) catch os.abort();
280 if (trace) |t| {280 if (trace) |t| {
lib/std/event/loop.zig+18-18
...@@ -21,12 +21,12 @@ pub const Loop = struct {...@@ -21,12 +21,12 @@ pub const Loop = struct {
21 os_data: OsData,21 os_data: OsData,
22 final_resume_node: ResumeNode,22 final_resume_node: ResumeNode,
23 pending_event_count: usize,23 pending_event_count: usize,
24 extra_threads: []*Thread,24 extra_threads: []Thread,
25 /// TODO change this to a pool of configurable number of threads25 /// TODO change this to a pool of configurable number of threads
26 /// and rename it to be not file-system-specific. it will become26 /// and rename it to be not file-system-specific. it will become
27 /// a thread pool for turning non-CPU-bound blocking things into27 /// a thread pool for turning non-CPU-bound blocking things into
28 /// async things. A fallback for any missing OS-specific API.28 /// async things. A fallback for any missing OS-specific API.
29 fs_thread: *Thread,29 fs_thread: Thread,
30 fs_queue: std.atomic.Queue(Request),30 fs_queue: std.atomic.Queue(Request),
31 fs_end_request: Request.Node,31 fs_end_request: Request.Node,
32 fs_thread_wakeup: std.Thread.ResetEvent,32 fs_thread_wakeup: std.Thread.ResetEvent,
...@@ -137,7 +137,7 @@ pub const Loop = struct {...@@ -137,7 +137,7 @@ pub const Loop = struct {
137 }137 }
138138
139 /// After initialization, call run().139 /// After initialization, call run().
140 /// This is the same as `initThreadPool` using `Thread.cpuCount` to determine the thread140 /// This is the same as `initThreadPool` using `Thread.getCpuCount` to determine the thread
141 /// pool size.141 /// pool size.
142 /// TODO copy elision / named return values so that the threads referencing *Loop142 /// TODO copy elision / named return values so that the threads referencing *Loop
143 /// have the correct pointer value.143 /// have the correct pointer value.
...@@ -145,7 +145,7 @@ pub const Loop = struct {...@@ -145,7 +145,7 @@ pub const Loop = struct {
145 pub fn initMultiThreaded(self: *Loop) !void {145 pub fn initMultiThreaded(self: *Loop) !void {
146 if (builtin.single_threaded)146 if (builtin.single_threaded)
147 @compileError("initMultiThreaded unavailable when building in single-threaded mode");147 @compileError("initMultiThreaded unavailable when building in single-threaded mode");
148 const core_count = try Thread.cpuCount();148 const core_count = try Thread.getCpuCount();
149 return self.initThreadPool(core_count);149 return self.initThreadPool(core_count);
150 }150 }
151151
...@@ -183,17 +183,17 @@ pub const Loop = struct {...@@ -183,17 +183,17 @@ pub const Loop = struct {
183 resume_node_count,183 resume_node_count,
184 );184 );
185185
186 self.extra_threads = try self.arena.allocator.alloc(*Thread, extra_thread_count);186 self.extra_threads = try self.arena.allocator.alloc(Thread, extra_thread_count);
187187
188 try self.initOsData(extra_thread_count);188 try self.initOsData(extra_thread_count);
189 errdefer self.deinitOsData();189 errdefer self.deinitOsData();
190190
191 if (!builtin.single_threaded) {191 if (!builtin.single_threaded) {
192 self.fs_thread = try Thread.spawn(posixFsRun, self);192 self.fs_thread = try Thread.spawn(.{}, posixFsRun, .{self});
193 }193 }
194 errdefer if (!builtin.single_threaded) {194 errdefer if (!builtin.single_threaded) {
195 self.posixFsRequest(&self.fs_end_request);195 self.posixFsRequest(&self.fs_end_request);
196 self.fs_thread.wait();196 self.fs_thread.join();
197 };197 };
198198
199 if (!std.builtin.single_threaded)199 if (!std.builtin.single_threaded)
...@@ -264,11 +264,11 @@ pub const Loop = struct {...@@ -264,11 +264,11 @@ pub const Loop = struct {
264 assert(amt == wakeup_bytes.len);264 assert(amt == wakeup_bytes.len);
265 while (extra_thread_index != 0) {265 while (extra_thread_index != 0) {
266 extra_thread_index -= 1;266 extra_thread_index -= 1;
267 self.extra_threads[extra_thread_index].wait();267 self.extra_threads[extra_thread_index].join();
268 }268 }
269 }269 }
270 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {270 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
271 self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self);271 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
272 }272 }
273 },273 },
274 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {274 .macos, .freebsd, .netbsd, .dragonfly, .openbsd => {
...@@ -329,11 +329,11 @@ pub const Loop = struct {...@@ -329,11 +329,11 @@ pub const Loop = struct {
329 _ = os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;329 _ = os.kevent(self.os_data.kqfd, final_kev_arr, empty_kevs, null) catch unreachable;
330 while (extra_thread_index != 0) {330 while (extra_thread_index != 0) {
331 extra_thread_index -= 1;331 extra_thread_index -= 1;
332 self.extra_threads[extra_thread_index].wait();332 self.extra_threads[extra_thread_index].join();
333 }333 }
334 }334 }
335 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {335 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
336 self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self);336 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
337 }337 }
338 },338 },
339 .windows => {339 .windows => {
...@@ -378,11 +378,11 @@ pub const Loop = struct {...@@ -378,11 +378,11 @@ pub const Loop = struct {
378 }378 }
379 while (extra_thread_index != 0) {379 while (extra_thread_index != 0) {
380 extra_thread_index -= 1;380 extra_thread_index -= 1;
381 self.extra_threads[extra_thread_index].wait();381 self.extra_threads[extra_thread_index].join();
382 }382 }
383 }383 }
384 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {384 while (extra_thread_index < extra_thread_count) : (extra_thread_index += 1) {
385 self.extra_threads[extra_thread_index] = try Thread.spawn(workerRun, self);385 self.extra_threads[extra_thread_index] = try Thread.spawn(.{}, workerRun, .{self});
386 }386 }
387 },387 },
388 else => {},388 else => {},
...@@ -651,18 +651,18 @@ pub const Loop = struct {...@@ -651,18 +651,18 @@ pub const Loop = struct {
651 .netbsd,651 .netbsd,
652 .dragonfly,652 .dragonfly,
653 .openbsd,653 .openbsd,
654 => self.fs_thread.wait(),654 => self.fs_thread.join(),
655 else => {},655 else => {},
656 }656 }
657 }657 }
658658
659 for (self.extra_threads) |extra_thread| {659 for (self.extra_threads) |extra_thread| {
660 extra_thread.wait();660 extra_thread.join();
661 }661 }
662662
663 @atomicStore(bool, &self.delay_queue.is_running, false, .SeqCst);663 @atomicStore(bool, &self.delay_queue.is_running, false, .SeqCst);
664 self.delay_queue.event.set();664 self.delay_queue.event.set();
665 self.delay_queue.thread.wait();665 self.delay_queue.thread.join();
666 }666 }
667667
668 /// Runs the provided function asynchronously. The function's frame is allocated668 /// Runs the provided function asynchronously. The function's frame is allocated
...@@ -787,7 +787,7 @@ pub const Loop = struct {...@@ -787,7 +787,7 @@ pub const Loop = struct {
787 const DelayQueue = struct {787 const DelayQueue = struct {
788 timer: std.time.Timer,788 timer: std.time.Timer,
789 waiters: Waiters,789 waiters: Waiters,
790 thread: *std.Thread,790 thread: std.Thread,
791 event: std.Thread.AutoResetEvent,791 event: std.Thread.AutoResetEvent,
792 is_running: bool,792 is_running: bool,
793793
...@@ -802,7 +802,7 @@ pub const Loop = struct {...@@ -802,7 +802,7 @@ pub const Loop = struct {
802 .event = std.Thread.AutoResetEvent{},802 .event = std.Thread.AutoResetEvent{},
803 .is_running = true,803 .is_running = true,
804 // Must be last so that it can read the other state, such as `is_running`.804 // Must be last so that it can read the other state, such as `is_running`.
805 .thread = try std.Thread.spawn(DelayQueue.run, self),805 .thread = try std.Thread.spawn(.{}, DelayQueue.run, .{self}),
806 };806 };
807 }807 }
808808
lib/std/fs/test.zig+5-6
...@@ -862,11 +862,10 @@ test "open file with exclusive lock twice, make sure it waits" {...@@ -862,11 +862,10 @@ test "open file with exclusive lock twice, make sure it waits" {
862 errdefer file.close();862 errdefer file.close();
863863
864 const S = struct {864 const S = struct {
865 const C = struct { dir: *fs.Dir, evt: *std.Thread.ResetEvent };865 fn checkFn(dir: *fs.Dir, evt: *std.Thread.ResetEvent) !void {
866 fn checkFn(ctx: C) !void {866 const file1 = try dir.createFile(filename, .{ .lock = .Exclusive });
867 const file1 = try ctx.dir.createFile(filename, .{ .lock = .Exclusive });
868 defer file1.close();867 defer file1.close();
869 ctx.evt.set();868 evt.set();
870 }869 }
871 };870 };
872871
...@@ -874,8 +873,8 @@ test "open file with exclusive lock twice, make sure it waits" {...@@ -874,8 +873,8 @@ test "open file with exclusive lock twice, make sure it waits" {
874 try evt.init();873 try evt.init();
875 defer evt.deinit();874 defer evt.deinit();
876875
877 const t = try std.Thread.spawn(S.checkFn, S.C{ .dir = &tmp.dir, .evt = &evt });876 const t = try std.Thread.spawn(.{}, S.checkFn, .{ &tmp.dir, &evt });
878 defer t.wait();877 defer t.join();
879878
880 const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms;879 const SLEEP_TIMEOUT_NS = 10 * std.time.ns_per_ms;
881 // Make sure we've slept enough.880 // Make sure we've slept enough.
lib/std/net/test.zig+5-5
...@@ -161,8 +161,8 @@ test "listen on a port, send bytes, receive bytes" {...@@ -161,8 +161,8 @@ test "listen on a port, send bytes, receive bytes" {
161 }161 }
162 };162 };
163163
164 const t = try std.Thread.spawn(S.clientFn, server.listen_address);164 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address});
165 defer t.wait();165 defer t.join();
166166
167 var client = try server.accept();167 var client = try server.accept();
168 defer client.stream.close();168 defer client.stream.close();
...@@ -277,7 +277,7 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -277,7 +277,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
277 try server.listen(socket_addr);277 try server.listen(socket_addr);
278278
279 const S = struct {279 const S = struct {
280 fn clientFn(_: void) !void {280 fn clientFn() !void {
281 const socket = try net.connectUnixSocket(socket_path);281 const socket = try net.connectUnixSocket(socket_path);
282 defer socket.close();282 defer socket.close();
283283
...@@ -285,8 +285,8 @@ test "listen on a unix socket, send bytes, receive bytes" {...@@ -285,8 +285,8 @@ test "listen on a unix socket, send bytes, receive bytes" {
285 }285 }
286 };286 };
287287
288 const t = try std.Thread.spawn(S.clientFn, {});288 const t = try std.Thread.spawn(.{}, S.clientFn, .{});
289 defer t.wait();289 defer t.join();
290290
291 var client = try server.accept();291 var client = try server.accept();
292 defer client.stream.close();292 defer client.stream.close();
lib/std/once.zig+4-4
...@@ -55,16 +55,16 @@ test "Once executes its function just once" {...@@ -55,16 +55,16 @@ test "Once executes its function just once" {
55 global_once.call();55 global_once.call();
56 global_once.call();56 global_once.call();
57 } else {57 } else {
58 var threads: [10]*std.Thread = undefined;58 var threads: [10]std.Thread = undefined;
59 defer for (threads) |handle| handle.wait();59 defer for (threads) |handle| handle.join();
6060
61 for (threads) |*handle| {61 for (threads) |*handle| {
62 handle.* = try std.Thread.spawn(struct {62 handle.* = try std.Thread.spawn(.{}, struct {
63 fn thread_fn(x: u8) void {63 fn thread_fn(x: u8) void {
64 _ = x;64 _ = x;
65 global_once.call();65 global_once.call();
66 }66 }
67 }.thread_fn, 0);67 }.thread_fn, .{0});
68 }68 }
69 }69 }
7070
lib/std/os/test.zig+19-30
...@@ -320,18 +320,9 @@ test "std.Thread.getCurrentId" {...@@ -320,18 +320,9 @@ test "std.Thread.getCurrentId" {
320 if (builtin.single_threaded) return error.SkipZigTest;320 if (builtin.single_threaded) return error.SkipZigTest;
321321
322 var thread_current_id: Thread.Id = undefined;322 var thread_current_id: Thread.Id = undefined;
323 const thread = try Thread.spawn(testThreadIdFn, &thread_current_id);323 const thread = try Thread.spawn(.{}, testThreadIdFn, .{&thread_current_id});
324 const thread_id = thread.handle();324 thread.join();
325 thread.wait();325 try expect(Thread.getCurrentId() != thread_current_id);
326 if (Thread.use_pthreads) {
327 try expect(thread_current_id == thread_id);
328 } else if (native_os == .windows) {
329 try expect(Thread.getCurrentId() != thread_current_id);
330 } else {
331 // If the thread completes very quickly, then thread_id can be 0. See the
332 // documentation comments for `std.Thread.handle`.
333 try expect(thread_id == 0 or thread_current_id == thread_id);
334 }
335}326}
336327
337test "spawn threads" {328test "spawn threads" {
...@@ -339,21 +330,20 @@ test "spawn threads" {...@@ -339,21 +330,20 @@ test "spawn threads" {
339330
340 var shared_ctx: i32 = 1;331 var shared_ctx: i32 = 1;
341332
342 const thread1 = try Thread.spawn(start1, {});333 const thread1 = try Thread.spawn(.{}, start1, .{});
343 const thread2 = try Thread.spawn(start2, &shared_ctx);334 const thread2 = try Thread.spawn(.{}, start2, .{&shared_ctx});
344 const thread3 = try Thread.spawn(start2, &shared_ctx);335 const thread3 = try Thread.spawn(.{}, start2, .{&shared_ctx});
345 const thread4 = try Thread.spawn(start2, &shared_ctx);336 const thread4 = try Thread.spawn(.{}, start2, .{&shared_ctx});
346337
347 thread1.wait();338 thread1.join();
348 thread2.wait();339 thread2.join();
349 thread3.wait();340 thread3.join();
350 thread4.wait();341 thread4.join();
351342
352 try expect(shared_ctx == 4);343 try expect(shared_ctx == 4);
353}344}
354345
355fn start1(ctx: void) u8 {346fn start1() u8 {
356 _ = ctx;
357 return 0;347 return 0;
358}348}
359349
...@@ -365,22 +355,21 @@ fn start2(ctx: *i32) u8 {...@@ -365,22 +355,21 @@ fn start2(ctx: *i32) u8 {
365test "cpu count" {355test "cpu count" {
366 if (native_os == .wasi) return error.SkipZigTest;356 if (native_os == .wasi) return error.SkipZigTest;
367357
368 const cpu_count = try Thread.cpuCount();358 const cpu_count = try Thread.getCpuCount();
369 try expect(cpu_count >= 1);359 try expect(cpu_count >= 1);
370}360}
371361
372test "thread local storage" {362test "thread local storage" {
373 if (builtin.single_threaded) return error.SkipZigTest;363 if (builtin.single_threaded) return error.SkipZigTest;
374 const thread1 = try Thread.spawn(testTls, {});364 const thread1 = try Thread.spawn(.{}, testTls, .{});
375 const thread2 = try Thread.spawn(testTls, {});365 const thread2 = try Thread.spawn(.{}, testTls, .{});
376 try testTls({});366 try testTls();
377 thread1.wait();367 thread1.join();
378 thread2.wait();368 thread2.join();
379}369}
380370
381threadlocal var x: i32 = 1234;371threadlocal var x: i32 = 1234;
382fn testTls(context: void) !void {372fn testTls() !void {
383 _ = context;
384 if (x != 1234) return error.TlsBadStartValue;373 if (x != 1234) return error.TlsBadStartValue;
385 x += 1;374 x += 1;
386 if (x != 1235) return error.TlsBadEndValue;375 if (x != 1235) return error.TlsBadEndValue;
lib/std/target.zig+19-4
...@@ -69,6 +69,13 @@ pub const Target = struct {...@@ -69,6 +69,13 @@ pub const Target = struct {
69 };69 };
70 }70 }
7171
72 pub fn isBSD(tag: Tag) bool {
73 return tag.isDarwin() or switch (tag) {
74 .kfreebsd, .freebsd, .openbsd, .netbsd, .dragonfly => true,
75 else => false,
76 };
77 }
78
72 pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 {79 pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 {
73 if (tag.isDarwin()) {80 if (tag.isDarwin()) {
74 return ".dylib";81 return ".dylib";
...@@ -787,6 +794,13 @@ pub const Target = struct {...@@ -787,6 +794,13 @@ pub const Target = struct {
787 };794 };
788 }795 }
789796
797 pub fn isAARCH64(arch: Arch) bool {
798 return switch (arch) {
799 .aarch64, .aarch64_be, .aarch64_32 => true,
800 else => false,
801 };
802 }
803
790 pub fn isThumb(arch: Arch) bool {804 pub fn isThumb(arch: Arch) bool {
791 return switch (arch) {805 return switch (arch) {
792 .thumb, .thumbeb => true,806 .thumb, .thumbeb => true,
...@@ -1365,10 +1379,7 @@ pub const Target = struct {...@@ -1365,10 +1379,7 @@ pub const Target = struct {
1365 }1379 }
13661380
1367 pub fn isAndroid(self: Target) bool {1381 pub fn isAndroid(self: Target) bool {
1368 return switch (self.abi) {1382 return self.abi == .android;
1369 .android => true,
1370 else => false,
1371 };
1372 }1383 }
13731384
1374 pub fn isWasm(self: Target) bool {1385 pub fn isWasm(self: Target) bool {
...@@ -1379,6 +1390,10 @@ pub const Target = struct {...@@ -1379,6 +1390,10 @@ pub const Target = struct {
1379 return self.os.tag.isDarwin();1390 return self.os.tag.isDarwin();
1380 }1391 }
13811392
1393 pub fn isBSD(self: Target) bool {
1394 return self.os.tag.isBSD();
1395 }
1396
1382 pub fn isGnuLibC_os_tag_abi(os_tag: Os.Tag, abi: Abi) bool {1397 pub fn isGnuLibC_os_tag_abi(os_tag: Os.Tag, abi: Abi) bool {
1383 return os_tag == .linux and abi.isGnu();1398 return os_tag == .linux and abi.isGnu();
1384 }1399 }
src/ThreadPool.zig+4-4
...@@ -21,7 +21,7 @@ const Runnable = struct {...@@ -21,7 +21,7 @@ const Runnable = struct {
2121
22const Worker = struct {22const Worker = struct {
23 pool: *ThreadPool,23 pool: *ThreadPool,
24 thread: *std.Thread,24 thread: std.Thread,
25 /// The node is for this worker only and must have an already initialized event25 /// The node is for this worker only and must have an already initialized event
26 /// when the thread is spawned.26 /// when the thread is spawned.
27 idle_node: IdleQueue.Node,27 idle_node: IdleQueue.Node,
...@@ -60,7 +60,7 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {...@@ -60,7 +60,7 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {
60 if (std.builtin.single_threaded)60 if (std.builtin.single_threaded)
61 return;61 return;
6262
63 const worker_count = std.math.max(1, std.Thread.cpuCount() catch 1);63 const worker_count = std.math.max(1, std.Thread.getCpuCount() catch 1);
64 self.workers = try allocator.alloc(Worker, worker_count);64 self.workers = try allocator.alloc(Worker, worker_count);
65 errdefer allocator.free(self.workers);65 errdefer allocator.free(self.workers);
6666
...@@ -74,13 +74,13 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {...@@ -74,13 +74,13 @@ pub fn init(self: *ThreadPool, allocator: *std.mem.Allocator) !void {
74 try worker.idle_node.data.init();74 try worker.idle_node.data.init();
75 errdefer worker.idle_node.data.deinit();75 errdefer worker.idle_node.data.deinit();
7676
77 worker.thread = try std.Thread.spawn(Worker.run, worker);77 worker.thread = try std.Thread.spawn(.{}, Worker.run, .{worker});
78 }78 }
79}79}
8080
81fn destroyWorkers(self: *ThreadPool, spawned: usize) void {81fn destroyWorkers(self: *ThreadPool, spawned: usize) void {
82 for (self.workers[0..spawned]) |*worker| {82 for (self.workers[0..spawned]) |*worker| {
83 worker.thread.wait();83 worker.thread.join();
84 worker.idle_node.data.deinit();84 worker.idle_node.data.deinit();
85 }85 }
86}86}
tools/update_cpu_features.zig+10-8
...@@ -816,18 +816,20 @@ pub fn main() anyerror!void {...@@ -816,18 +816,20 @@ pub fn main() anyerror!void {
816 });816 });
817 }817 }
818 } else {818 } else {
819 var threads = try arena.alloc(*std.Thread, llvm_targets.len);819 var threads = try arena.alloc(std.Thread, llvm_targets.len);
820 for (llvm_targets) |llvm_target, i| {820 for (llvm_targets) |llvm_target, i| {
821 threads[i] = try std.Thread.spawn(processOneTarget, .{821 threads[i] = try std.Thread.spawn(.{}, processOneTarget, .{
822 .llvm_tblgen_exe = llvm_tblgen_exe,822 Job{
823 .llvm_src_root = llvm_src_root,823 .llvm_tblgen_exe = llvm_tblgen_exe,
824 .zig_src_dir = zig_src_dir,824 .llvm_src_root = llvm_src_root,
825 .root_progress = root_progress,825 .zig_src_dir = zig_src_dir,
826 .llvm_target = llvm_target,826 .root_progress = root_progress,
827 .llvm_target = llvm_target,
828 },
827 });829 });
828 }830 }
829 for (threads) |thread| {831 for (threads) |thread| {
830 thread.wait();832 thread.join();
831 }833 }
832 }834 }
833}835}