authorgravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2021-06-28 11:27:23-05:00
committergravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2021-06-30 21:49:00-05:00
logf0fa129e9b1cdbd90b231da14c6cd99c9413aa98
tree4e54d26a4ec212aefc91d36ec3a9391c881973fe
parent7b323f84ca876c86bbe06f132d5a5d3775def3a2

std.Thread: more cleanup & testing


5 files changed, 184 insertions(+), 146 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+173-127
...@@ -24,17 +24,6 @@ pub const Condition = @import("Thread/Condition.zig");...@@ -24,17 +24,6 @@ pub const Condition = @import("Thread/Condition.zig");
2424
25pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");25pub const spinLoopHint = @compileError("deprecated: use std.atomic.spinLoopHint");
2626
27test "std.Thread" {
28 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
29 _ = AutoResetEvent;
30 _ = Futex;
31 _ = ResetEvent;
32 _ = StaticResetEvent;
33 _ = Mutex;
34 _ = Semaphore;
35 _ = Condition;
36}
37
38pub const use_pthreads = target.os.tag != .windows and std.builtin.link_libc;27pub const use_pthreads = target.os.tag != .windows and std.builtin.link_libc;
3928
40const Thread = @This();29const Thread = @This();
...@@ -50,7 +39,6 @@ else...@@ -50,7 +39,6 @@ else
50impl: Impl,39impl: Impl,
5140
52/// Represents a unique ID per thread.41/// Represents a unique ID per thread.
53/// May be an integer or pointer depending on the platform.
54pub const Id = u64;42pub const Id = u64;
5543
56/// Returns the platform ID of the callers thread.44/// Returns the platform ID of the callers thread.
...@@ -79,7 +67,7 @@ pub const SpawnConfig = struct {...@@ -79,7 +67,7 @@ pub const SpawnConfig = struct {
79 stack_size: usize = 16 * 1024 * 1024,67 stack_size: usize = 16 * 1024 * 1024,
80};68};
8169
82pub const SpawnError = error {70pub const SpawnError = error{
83 /// A system-imposed limit on the number of threads was encountered.71 /// A system-imposed limit on the number of threads was encountered.
84 /// There are a number of limits that may trigger this error:72 /// There are a number of limits that may trigger this error:
85 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),73 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
...@@ -115,7 +103,7 @@ pub const SpawnError = error {...@@ -115,7 +103,7 @@ pub const SpawnError = error {
115/// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion`.103/// or call `detach()` to excuse the caller from calling `join()` and have the thread clean up its resources on completion`.
116pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread {104pub fn spawn(config: SpawnConfig, comptime function: anytype, args: anytype) SpawnError!Thread {
117 if (std.builtin.single_threaded) {105 if (std.builtin.single_threaded) {
118 @compileError("cannot spawn thread when building in single-threaded mode");106 @compileError("Cannot spawn thread when building in single-threaded mode");
119 }107 }
120108
121 const impl = try Impl.spawn(config, function, args);109 const impl = try Impl.spawn(config, function, args);
...@@ -132,11 +120,13 @@ pub fn getHandle(self: Thread) Handle {...@@ -132,11 +120,13 @@ pub fn getHandle(self: Thread) Handle {
132}120}
133121
134/// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion.122/// Release the obligation of the caller to call `join()` and have the thread clean up its own resources on completion.
123/// Once called, this consumes the Thread object and invoking any other functions on it is considered undefined behavior.
135pub fn detach(self: Thread) void {124pub fn detach(self: Thread) void {
136 return self.impl.detach();125 return self.impl.detach();
137}126}
138127
139/// Waits for the thread to complete, then deallocates any resources created on `spawn()`.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.
140pub fn join(self: Thread) void {130pub fn join(self: Thread) void {
141 return self.impl.join();131 return self.impl.join();
142}132}
...@@ -200,6 +190,8 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {...@@ -200,6 +190,8 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
200 }190 }
201}191}
202192
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.
203const UnsupportedImpl = struct {195const UnsupportedImpl = struct {
204 pub const ThreadHandle = void;196 pub const ThreadHandle = void;
205197
...@@ -212,7 +204,7 @@ const UnsupportedImpl = struct {...@@ -212,7 +204,7 @@ const UnsupportedImpl = struct {
212 }204 }
213205
214 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {206 fn spawn(config: SpawnConfig, comptime f: anytype, args: anytype) !Impl {
215 return unsupported(.{config, f, args});207 return unsupported(.{ config, f, args });
216 }208 }
217209
218 fn getHandle(self: Impl) ThreadHandle {210 fn getHandle(self: Impl) ThreadHandle {
...@@ -225,7 +217,7 @@ const UnsupportedImpl = struct {...@@ -225,7 +217,7 @@ const UnsupportedImpl = struct {
225217
226 fn join(self: Impl) void {218 fn join(self: Impl) void {
227 return unsupported(self);219 return unsupported(self);
228 } 220 }
229221
230 fn unsupported(unusued: anytype) noreturn {222 fn unsupported(unusued: anytype) noreturn {
231 @compileLog("Unsupported operating system", target.os.tag);223 @compileLog("Unsupported operating system", target.os.tag);
...@@ -244,6 +236,7 @@ const WindowsThreadImpl = struct {...@@ -244,6 +236,7 @@ const WindowsThreadImpl = struct {
244 }236 }
245237
246 fn getCpuCount() !usize {238 fn getCpuCount() !usize {
239 // Faster than calling into GetSystemInfo(), even if amortized.
247 return windows.peb().NumberOfProcessors;240 return windows.peb().NumberOfProcessors;
248 }241 }
249242
...@@ -299,16 +292,17 @@ const WindowsThreadImpl = struct {...@@ -299,16 +292,17 @@ const WindowsThreadImpl = struct {
299 // Its also fine if the limit here is incorrect as stack size is only a hint.292 // Its also fine if the limit here is incorrect as stack size is only a hint.
300 var stack_size = std.math.cast(u32, config.stack_size) catch std.math.maxInt(u32);293 var stack_size = std.math.cast(u32, config.stack_size) catch std.math.maxInt(u32);
301 stack_size = std.math.max(64 * 1024, stack_size);294 stack_size = std.math.max(64 * 1024, stack_size);
302 295
303 instance.thread.thread_handle = windows.kernel32.CreateThread(296 instance.thread.thread_handle = windows.kernel32.CreateThread(
304 null, 297 null,
305 stack_size, 298 stack_size,
306 Instance.entryFn, 299 Instance.entryFn,
307 @ptrCast(*c_void, instance), 300 @ptrCast(*c_void, instance),
308 0, 301 0,
309 null,302 null,
310 ) orelse {303 ) orelse {
311 return windows.unexpectedError(windows.kernel32.GetLastError());304 const errno = windows.kernel32.GetLastError();
305 return windows.unexpectedError(errno);
312 };306 };
313307
314 return Impl{ .thread = &instance.thread };308 return Impl{ .thread = &instance.thread };
...@@ -332,7 +326,7 @@ const WindowsThreadImpl = struct {...@@ -332,7 +326,7 @@ const WindowsThreadImpl = struct {
332 windows.CloseHandle(self.thread.thread_handle);326 windows.CloseHandle(self.thread.thread_handle);
333 assert(self.thread.completion.load(.SeqCst) == .completed);327 assert(self.thread.completion.load(.SeqCst) == .completed);
334 self.thread.free();328 self.thread.free();
335 } 329 }
336};330};
337331
338const PosixThreadImpl = struct {332const PosixThreadImpl = struct {
...@@ -374,7 +368,9 @@ const PosixThreadImpl = struct {...@@ -374,7 +368,9 @@ const PosixThreadImpl = struct {
374368
375 fn getCpuCount() !usize {369 fn getCpuCount() !usize {
376 switch (target.os.tag) {370 switch (target.os.tag) {
377 .linux => return LinuxThreadImpl.getCpuCount(),371 .linux => {
372 return LinuxThreadImpl.getCpuCount();
373 },
378 .openbsd => {374 .openbsd => {
379 var count: c_int = undefined;375 var count: c_int = undefined;
380 var count_size: usize = @sizeOf(c_int);376 var count_size: usize = @sizeOf(c_int);
...@@ -413,6 +409,7 @@ const PosixThreadImpl = struct {...@@ -413,6 +409,7 @@ const PosixThreadImpl = struct {
413409
414 const Instance = struct {410 const Instance = struct {
415 fn entryFn(raw_arg: ?*c_void) callconv(.C) ?*c_void {411 fn entryFn(raw_arg: ?*c_void) callconv(.C) ?*c_void {
412 // @alignCast() below doesn't support zero-sized-types (ZST)
416 if (@sizeOf(Args) < 1) {413 if (@sizeOf(Args) < 1) {
417 return callFn(f, @as(Args, undefined));414 return callFn(f, @as(Args, undefined));
418 }415 }
...@@ -457,8 +454,9 @@ const PosixThreadImpl = struct {...@@ -457,8 +454,9 @@ const PosixThreadImpl = struct {
457454
458 fn detach(self: Impl) void {455 fn detach(self: Impl) void {
459 switch (c.pthread_detach(self.handle)) {456 switch (c.pthread_detach(self.handle)) {
460 os.EINVAL => unreachable,457 0 => {},
461 os.ESRCH => unreachable,458 os.EINVAL => unreachable, // thread handle is not joinable
459 os.ESRCH => unreachable, // thread handle is invalid
462 else => unreachable,460 else => unreachable,
463 }461 }
464 }462 }
...@@ -466,9 +464,9 @@ const PosixThreadImpl = struct {...@@ -466,9 +464,9 @@ const PosixThreadImpl = struct {
466 fn join(self: Impl) void {464 fn join(self: Impl) void {
467 switch (c.pthread_join(self.handle, null)) {465 switch (c.pthread_join(self.handle, null)) {
468 0 => {},466 0 => {},
469 os.EINVAL => unreachable,467 os.EINVAL => unreachable, // thread handle is not joinable (or another thread is already joining in)
470 os.ESRCH => unreachable,468 os.ESRCH => unreachable, // thread handle is invalid
471 os.EDEADLK => unreachable,469 os.EDEADLK => unreachable, // two threads tried to join each other
472 else => unreachable,470 else => unreachable,
473 }471 }
474 }472 }
...@@ -476,7 +474,7 @@ const PosixThreadImpl = struct {...@@ -476,7 +474,7 @@ const PosixThreadImpl = struct {
476474
477const LinuxThreadImpl = struct {475const LinuxThreadImpl = struct {
478 const linux = os.linux;476 const linux = os.linux;
479 477
480 pub const ThreadHandle = i32;478 pub const ThreadHandle = i32;
481479
482 threadlocal var tls_thread_id: ?Id = null;480 threadlocal var tls_thread_id: ?Id = null;
...@@ -491,7 +489,8 @@ const LinuxThreadImpl = struct {...@@ -491,7 +489,8 @@ const LinuxThreadImpl = struct {
491489
492 fn getCpuCount() !usize {490 fn getCpuCount() !usize {
493 const cpu_set = try os.sched_getaffinity(0);491 const cpu_set = try os.sched_getaffinity(0);
494 return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast492 // TODO: should not need this usize cast
493 return @as(usize, os.CPU_COUNT(cpu_set));
495 }494 }
496495
497 thread: *ThreadCompletion,496 thread: *ThreadCompletion,
...@@ -547,7 +546,7 @@ const LinuxThreadImpl = struct {...@@ -547,7 +546,7 @@ const LinuxThreadImpl = struct {
547 bytes = std.mem.alignForward(bytes, std.mem.page_size);546 bytes = std.mem.alignForward(bytes, std.mem.page_size);
548 break :blk bytes;547 break :blk bytes;
549 };548 };
550 549
551 // map all memory needed without read/write permissions550 // map all memory needed without read/write permissions
552 // to avoid committing the whole region right away551 // to avoid committing the whole region right away
553 const mapped = os.mmap(552 const mapped = os.mmap(
...@@ -654,7 +653,7 @@ const LinuxThreadImpl = struct {...@@ -654,7 +653,7 @@ const LinuxThreadImpl = struct {
654653
655 switch (linux.getErrno(linux.futex_wait(654 switch (linux.getErrno(linux.futex_wait(
656 &self.thread.child_tid.value,655 &self.thread.child_tid.value,
657 linux.FUTEX_WAIT, 656 linux.FUTEX_WAIT,
658 tid,657 tid,
659 null,658 null,
660 ))) {659 ))) {
...@@ -671,98 +670,145 @@ const LinuxThreadImpl = struct {...@@ -671,98 +670,145 @@ const LinuxThreadImpl = struct {
671 extern fn __unmap_and_exit(ptr: usize, len: usize) callconv(.C) noreturn;670 extern fn __unmap_and_exit(ptr: usize, len: usize) callconv(.C) noreturn;
672 comptime {671 comptime {
673 if (target.os.tag == .linux) {672 if (target.os.tag == .linux) {
674 asm(switch (target.cpu.arch) {673 asm (switch (target.cpu.arch) {
675 .i386 => (674 .i386 => (
676 \\.text675 \\.text
677 \\.global __unmap_and_exit676 \\.global __unmap_and_exit
678 \\.type __unmap_and_exit, @function677 \\.type __unmap_and_exit, @function
679 \\__unmap_and_exit:678 \\__unmap_and_exit:
680 \\ movl $91, %eax679 \\ movl $91, %eax
681 \\ movl 4(%esp), %ebx680 \\ movl 4(%esp), %ebx
682 \\ movl 8(%esp), %ecx681 \\ movl 8(%esp), %ecx
683 \\ int $128682 \\ int $128
684 \\ xorl %ebx, %ebx683 \\ xorl %ebx, %ebx
685 \\ movl $1, %eax684 \\ movl $1, %eax
686 \\ int $128685 \\ int $128
687 ),686 ),
688 .x86_64 => (687 .x86_64 => (
689 \\.text688 \\.text
690 \\.global __unmap_and_exit689 \\.global __unmap_and_exit
691 \\.type __unmap_and_exit, @function690 \\.type __unmap_and_exit, @function
692 \\__unmap_and_exit:691 \\__unmap_and_exit:
693 \\ movl $11, %eax692 \\ movl $11, %eax
694 \\ syscall693 \\ syscall
695 \\ xor %rdi, %rdi694 \\ xor %rdi, %rdi
696 \\ movl $60, %eax695 \\ movl $60, %eax
697 \\ syscall696 \\ syscall
698 ),697 ),
699 .arm, .armeb, .thumb, .thumbeb => (698 .arm, .armeb, .thumb, .thumbeb => (
700 \\.syntax unified699 \\.syntax unified
701 \\.text700 \\.text
702 \\.global __unmap_and_exit701 \\.global __unmap_and_exit
703 \\.type __unmap_and_exit, %function702 \\.type __unmap_and_exit, %function
704 \\__unmap_and_exit:703 \\__unmap_and_exit:
705 \\ mov r7, #91704 \\ mov r7, #91
706 \\ svc 0705 \\ svc 0
707 \\ mov r7, #1706 \\ mov r7, #1
708 \\ svc 0707 \\ svc 0
709 ),708 ),
710 .aarch64, .aarch64_be, .aarch64_32 => (709 .aarch64, .aarch64_be, .aarch64_32 => (
711 \\.global __unmap_and_exit710 \\.global __unmap_and_exit
712 \\.type __unmap_and_exit, %function711 \\.type __unmap_and_exit, %function
713 \\__unmap_and_exit:712 \\__unmap_and_exit:
714 \\ mov x8, #215713 \\ mov x8, #215
715 \\ svc 0714 \\ svc 0
716 \\ mov x8, #93715 \\ mov x8, #93
717 \\ svc 0716 \\ svc 0
718 ),717 ),
719 .mips, .mipsel, => (718 .mips,
720 \\.set noreorder719 .mipsel,
721 \\.global __unmap_and_exit720 => (
722 \\.type __unmap_and_exit,@function721 \\.set noreorder
723 \\__unmap_and_exit:722 \\.global __unmap_and_exit
724 \\ move $sp, $25723 \\.type __unmap_and_exit,@function
725 \\ li $2, 4091724 \\__unmap_and_exit:
726 \\ syscall725 \\ move $sp, $25
727 \\ li $4, 0726 \\ li $2, 4091
728 \\ li $2, 4001727 \\ syscall
729 \\ syscall728 \\ li $4, 0
730 ),729 \\ li $2, 4001
731 .mips64, .mips64el => (730 \\ syscall
732 \\.set noreorder731 ),
733 \\.global __unmap_and_exit732 .mips64, .mips64el => (
734 \\.type __unmap_and_exit, @function733 \\.set noreorder
735 \\__unmap_and_exit:734 \\.global __unmap_and_exit
736 \\ li $2, 4091735 \\.type __unmap_and_exit, @function
737 \\ syscall736 \\__unmap_and_exit:
738 \\ li $4, 0737 \\ li $2, 4091
739 \\ li $2, 4001738 \\ syscall
740 \\ syscall739 \\ li $4, 0
741 ),740 \\ li $2, 4001
742 .powerpc, .powerpc64, .powerpc64le => (741 \\ syscall
743 \\.text742 ),
744 \\.global __unmap_and_exit743 .powerpc, .powerpc64, .powerpc64le => (
745 \\.type __unmap_and_exit, %function744 \\.text
746 \\__unmap_and_exit:745 \\.global __unmap_and_exit
747 \\ li 0, 91746 \\.type __unmap_and_exit, %function
748 \\ sc747 \\__unmap_and_exit:
749 \\ li 0, 1748 \\ li 0, 91
750 \\ sc749 \\ sc
751 \\ blr750 \\ li 0, 1
752 ),751 \\ sc
753 .riscv64 => (752 \\ blr
754 \\.global __unmap_and_exit753 ),
755 \\.type __unmap_and_exit, %function754 .riscv64 => (
756 \\__unmap_and_exit:755 \\.global __unmap_and_exit
757 \\ li a7, 215756 \\.type __unmap_and_exit, %function
758 \\ ecall757 \\__unmap_and_exit:
759 \\ li a7, 93758 \\ li a7, 215
760 \\ ecall759 \\ ecall
761 ),760 \\ li a7, 93
762 else => |cpu_arch| {761 \\ ecall
763 @compileLog("linux arch", cpu_arch, "is not supported");762 ),
764 },763 else => |cpu_arch| {
765 });764 @compileLog("linux arch", cpu_arch, "is not supported");
765 },
766 });
766 }767 }
767 }768 }
768};
\ No newline at end of file
769};
770
771test "std.Thread" {
772 // Doesn't use testing.refAllDecls() since that would pull in the compileError spinLoopHint.
773 _ = AutoResetEvent;
774 _ = Futex;
775 _ = ResetEvent;
776 _ = StaticResetEvent;
777 _ = Mutex;
778 _ = Semaphore;
779 _ = Condition;
780}
781
782fn testIncrementNotify(value: *usize, event: *ResetEvent) void {
783 value.* += 1;
784 event.set();
785}
786
787test "Thread.join" {
788 if (std.builtin.single_threaded) return error.SkipZigTest;
789
790 var value: usize = 0;
791 var event: ResetEvent = undefined;
792 try event.init();
793 defer event.deinit();
794
795 const thread = try Thread.spawn(.{}, testIncrementNotify, .{&value, &event});
796 thread.join();
797
798 try std.testing.expectEqual(value, 1);
799}
800
801test "Thread.detach" {
802 if (std.builtin.single_threaded) return error.SkipZigTest;
803
804 var value: usize = 0;
805 var event: ResetEvent = undefined;
806 try event.init();
807 defer event.deinit();
808
809 const thread = try Thread.spawn(.{}, testIncrementNotify, .{&value, &event});
810 thread.detach();
811
812 event.wait();
813 try std.testing.expectEqual(value, 1);
814}
\ No newline at end of file
lib/std/Thread/Futex.zig+3-3
...@@ -407,7 +407,7 @@ test "Futex - wait/wake" {...@@ -407,7 +407,7 @@ test "Futex - wait/wake" {
407407
408test "Futex - Signal" {408test "Futex - Signal" {
409 if (single_threaded) {409 if (single_threaded) {
410 return;410 return error.SkipZigTest;
411 }411 }
412412
413 const Paddle = struct {413 const Paddle = struct {
...@@ -449,7 +449,7 @@ test "Futex - Signal" {...@@ -449,7 +449,7 @@ test "Futex - Signal" {
449449
450test "Futex - Broadcast" {450test "Futex - Broadcast" {
451 if (single_threaded) {451 if (single_threaded) {
452 return;452 return error.SkipZigTest;
453 }453 }
454454
455 const Context = struct {455 const Context = struct {
...@@ -506,7 +506,7 @@ test "Futex - Broadcast" {...@@ -506,7 +506,7 @@ test "Futex - Broadcast" {
506506
507test "Futex - Chain" {507test "Futex - Chain" {
508 if (single_threaded) {508 if (single_threaded) {
509 return;509 return error.SkipZigTest;
510 }510 }
511511
512 const Signal = struct {512 const Signal = struct {
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/os/test.zig+1-10
...@@ -321,17 +321,8 @@ test "std.Thread.getCurrentId" {...@@ -321,17 +321,8 @@ test "std.Thread.getCurrentId" {
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.getHandle();
325 thread.join();324 thread.join();
326 if (Thread.use_pthreads) {325 try expect(Thread.getCurrentId() != thread_current_id);
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" {