| ... | ... | @@ -1,3 +1,7 @@ |
| 1 | const builtin = @import("builtin"); |
| 2 | const AtomicOrder = builtin.AtomicOrder; |
| 3 | const AtomicRmwOp = builtin.AtomicRmwOp; |
| 4 | |
| 1 | 5 | /// Many reader, many writer, non-allocating, thread-safe, lock-free |
| 2 | 6 | pub fn Queue(comptime T: type) type { |
| 3 | 7 | return struct { |
| ... | ... | @@ -12,7 +16,7 @@ pub fn Queue(comptime T: type) type { |
| 12 | 16 | data: T, |
| 13 | 17 | }; |
| 14 | 18 | |
| 15 | | // TODO: well defined copy elision |
| 19 | // TODO: well defined copy elision: https://github.com/zig-lang/zig/issues/287 |
| 16 | 20 | pub fn init(self: &Self) void { |
| 17 | 21 | self.root.next = null; |
| 18 | 22 | self.head = &self.root; |
| ... | ... | @@ -35,3 +39,82 @@ pub fn Queue(comptime T: type) type { |
| 35 | 39 | } |
| 36 | 40 | }; |
| 37 | 41 | } |
| 42 | |
| 43 | const std = @import("std"); |
| 44 | const Context = struct { |
| 45 | allocator: &std.mem.Allocator, |
| 46 | queue: &Queue(i32), |
| 47 | put_sum: isize, |
| 48 | get_sum: isize, |
| 49 | get_count: usize, |
| 50 | puts_done: u8, // TODO make this a bool |
| 51 | }; |
| 52 | const puts_per_thread = 10000; |
| 53 | const put_thread_count = 3; |
| 54 | |
| 55 | test "std.atomic.queue" { |
| 56 | var direct_allocator = std.heap.DirectAllocator.init(); |
| 57 | defer direct_allocator.deinit(); |
| 58 | |
| 59 | var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 64 * 1024 * 1024); |
| 60 | defer direct_allocator.allocator.free(plenty_of_memory); |
| 61 | |
| 62 | var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); |
| 63 | var a = &fixed_buffer_allocator.allocator; |
| 64 | |
| 65 | var queue: Queue(i32) = undefined; |
| 66 | queue.init(); |
| 67 | var context = Context { |
| 68 | .allocator = a, |
| 69 | .queue = &queue, |
| 70 | .put_sum = 0, |
| 71 | .get_sum = 0, |
| 72 | .puts_done = 0, |
| 73 | .get_count = 0, |
| 74 | }; |
| 75 | |
| 76 | var putters: [put_thread_count]&std.os.Thread = undefined; |
| 77 | for (putters) |*t| { |
| 78 | *t = try std.os.spawnThreadAllocator(a, &context, startPuts); |
| 79 | } |
| 80 | var getters: [put_thread_count]&std.os.Thread = undefined; |
| 81 | for (getters) |*t| { |
| 82 | *t = try std.os.spawnThreadAllocator(a, &context, startGets); |
| 83 | } |
| 84 | |
| 85 | for (putters) |t| t.wait(); |
| 86 | _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); |
| 87 | for (getters) |t| t.wait(); |
| 88 | |
| 89 | std.debug.assert(context.put_sum == context.get_sum); |
| 90 | std.debug.assert(context.get_count == puts_per_thread * put_thread_count); |
| 91 | } |
| 92 | |
| 93 | fn startPuts(ctx: &Context) u8 { |
| 94 | var put_count: usize = puts_per_thread; |
| 95 | var r = std.rand.DefaultPrng.init(0xdeadbeef); |
| 96 | while (put_count != 0) : (put_count -= 1) { |
| 97 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz |
| 98 | const x = @bitCast(i32, r.random.scalar(u32)); |
| 99 | const node = ctx.allocator.create(Queue(i32).Node) catch unreachable; |
| 100 | node.data = x; |
| 101 | ctx.queue.put(node); |
| 102 | _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); |
| 103 | } |
| 104 | return 0; |
| 105 | } |
| 106 | |
| 107 | fn startGets(ctx: &Context) u8 { |
| 108 | while (true) { |
| 109 | while (ctx.queue.get()) |node| { |
| 110 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz |
| 111 | _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); |
| 112 | _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); |
| 113 | } |
| 114 | |
| 115 | if (@atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1) { |
| 116 | break; |
| 117 | } |
| 118 | } |
| 119 | return 0; |
| 120 | } |