diff --git a/lib/std/once.zig b/lib/std/once.zig new file mode 100644 index 000000000..2a1a02f17 --- /dev/null +++ b/lib/std/once.zig @@ -0,0 +1,66 @@ +const std = @import("std.zig"); +const builtin = std.builtin; +const testing = std.testing; + +pub fn once(comptime f: fn () void) Once(f) { + return Once(f){}; +} + +/// An object that executes the function `f` just once. +pub fn Once(comptime f: fn () void) type { + return struct { + done: bool = false, + mutex: std.Mutex = std.Mutex.init(), + + /// Call the function `f`. + /// If `call` is invoked multiple times `f` will be executed only the + /// first time. + /// The invocations are thread-safe. + pub fn call(self: *@This()) void { + if (@atomicLoad(bool, &self.done, .Acquire)) + return; + + return self.callSlow(); + } + + fn callSlow(self: *@This()) void { + @setCold(true); + + const T = self.mutex.acquire(); + defer T.release(); + + // The first thread to acquire the mutex gets to run the initializer + if (!self.done) { + f(); + @atomicStore(bool, &self.done, true, .Release); + } + } + }; +} + +var global_number: i32 = 0; +var global_once = once(incr); + +fn incr() void { + global_number += 1; +} + +test "Once executes its function just once" { + if (builtin.single_threaded) { + global_once.call(); + global_once.call(); + } else { + var threads: [10]*std.Thread = undefined; + defer for (threads) |handle| handle.wait(); + + for (threads) |*handle| { + handle.* = try std.Thread.spawn(@as(u8, 0), struct { + fn thread_fn(x: u8) void { + global_once.call(); + } + }.thread_fn); + } + } + + testing.expectEqual(@as(i32, 1), global_number); +} diff --git a/lib/std/std.zig b/lib/std/std.zig index 4ea8e7170..01c0af752 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -52,6 +52,7 @@ pub const mem = @import("mem.zig"); pub const meta = @import("meta.zig"); pub const net = @import("net.zig"); pub const os = @import("os.zig"); +pub const once = @import("once.zig").once; pub const packed_int_array = @import("packed_int_array.zig"); pub const pdb = @import("pdb.zig"); pub const process = @import("process.zig");