| ... | @@ -1,3 +1,7 @@ | ... | @@ -1,3 +1,7 @@ |
| | 1 | const builtin = @import("builtin"); |
| | 2 | const AtomicOrder = builtin.AtomicOrder; |
| | 3 | const AtomicRmwOp = builtin.AtomicRmwOp; |
| | 4 | |
| 1 | /// Many reader, many writer, non-allocating, thread-safe, lock-free | 5 | /// Many reader, many writer, non-allocating, thread-safe, lock-free |
| 2 | pub fn Queue(comptime T: type) type { | 6 | pub fn Queue(comptime T: type) type { |
| 3 | return struct { | 7 | return struct { |
| ... | @@ -12,7 +16,7 @@ pub fn Queue(comptime T: type) type { | ... | @@ -12,7 +16,7 @@ pub fn Queue(comptime T: type) type { |
| 12 | data: T, | 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 | pub fn init(self: &Self) void { | 20 | pub fn init(self: &Self) void { |
| 17 | self.root.next = null; | 21 | self.root.next = null; |
| 18 | self.head = &self.root; | 22 | self.head = &self.root; |
| ... | @@ -35,3 +39,82 @@ pub fn Queue(comptime T: type) type { | ... | @@ -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 | } |