...
1const std = @import("std.zig");
2const builtin = std.builtin;
3const testing = std.testing;
4
5pub fn once(comptime f: fn () void) Once(f) {
6 return Once(f){};
7}
8
9/// An object that executes the function `f` just once.
10pub fn Once(comptime f: fn () void) type {
11 return struct {
12 done: bool = false,
13 mutex: std.Mutex = std.Mutex.init(),
14
15 /// Call the function `f`.
16 /// If `call` is invoked multiple times `f` will be executed only the
17 /// first time.
18 /// The invocations are thread-safe.
19 pub fn call(self: *@This()) void {
20 if (@atomicLoad(bool, &self.done, .Acquire))
21 return;
22
23 return self.callSlow();
24 }
25
26 fn callSlow(self: *@This()) void {
27 @setCold(true);
28
29 const T = self.mutex.acquire();
30 defer T.release();
31
32 // The first thread to acquire the mutex gets to run the initializer
33 if (!self.done) {
34 f();
35 @atomicStore(bool, &self.done, true, .Release);
36 }
37 }
38 };
39}
40
41var global_number: i32 = 0;
42var global_once = once(incr);
43
44fn incr() void {
45 global_number += 1;
46}
47
48test "Once executes its function just once" {
49 if (builtin.single_threaded) {
50 global_once.call();
51 global_once.call();
52 } else {
53 var threads: [10]*std.Thread = undefined;
54 defer for (threads) |handle| handle.wait();
55
56 for (threads) |*handle| {
57 handle.* = try std.Thread.spawn(@as(u8, 0), struct {
58 fn thread_fn(x: u8) void {
59 global_once.call();
60 }
61 }.thread_fn);
62 }
63 }
64
65 testing.expectEqual(@as(i32, 1), global_number);
66}
View as plain text