zig/std/statically_initialized_mute...

109 lines
3.7 KiB
Zig

const std = @import("std.zig");
const builtin = @import("builtin");
const AtomicOrder = builtin.AtomicOrder;
const AtomicRmwOp = builtin.AtomicRmwOp;
const assert = std.debug.assert;
const expect = std.testing.expect;
const windows = std.os.windows;
/// Lock may be held only once. If the same thread
/// tries to acquire the same mutex twice, it deadlocks.
/// This type is intended to be initialized statically. If you don't
/// require static initialization, use std.Mutex.
/// On Windows, this mutex allocates resources when it is
/// first used, and the resources cannot be freed.
/// On Linux, this is an alias of std.Mutex.
pub const StaticallyInitializedMutex = switch (builtin.os) {
builtin.Os.linux => std.Mutex,
builtin.Os.windows => struct {
lock: windows.CRITICAL_SECTION,
init_once: windows.RTL_RUN_ONCE,
pub const Held = struct {
mutex: *StaticallyInitializedMutex,
pub fn release(self: Held) void {
windows.LeaveCriticalSection(&self.mutex.lock);
}
};
pub fn init() StaticallyInitializedMutex {
return StaticallyInitializedMutex{
.lock = undefined,
.init_once = windows.INIT_ONCE_STATIC_INIT,
};
}
extern fn initCriticalSection(
InitOnce: *windows.RTL_RUN_ONCE,
Parameter: ?*c_void,
Context: ?*c_void,
) windows.BOOL {
const lock = @ptrCast(*windows.CRITICAL_SECTION, @alignCast(@alignOf(windows.CRITICAL_SECTION), Parameter));
windows.InitializeCriticalSection(lock);
return windows.TRUE;
}
/// TODO: once https://github.com/ziglang/zig/issues/287 is solved and std.Mutex has a better
/// implementation of a runtime initialized mutex, remove this function.
pub fn deinit(self: *StaticallyInitializedMutex) void {
assert(windows.InitOnceExecuteOnce(&self.init_once, initCriticalSection, &self.lock, null) != 0);
windows.DeleteCriticalSection(&self.lock);
}
pub fn acquire(self: *StaticallyInitializedMutex) Held {
assert(windows.InitOnceExecuteOnce(&self.init_once, initCriticalSection, &self.lock, null) != 0);
windows.EnterCriticalSection(&self.lock);
return Held{ .mutex = self };
}
},
else => std.Mutex,
};
test "std.StaticallyInitializedMutex" {
const TestContext = struct {
data: i128,
const TestContext = @This();
const incr_count = 10000;
var mutex = StaticallyInitializedMutex.init();
fn worker(ctx: *TestContext) void {
var i: usize = 0;
while (i != TestContext.incr_count) : (i += 1) {
const held = mutex.acquire();
defer held.release();
ctx.data += 1;
}
}
};
var direct_allocator = std.heap.DirectAllocator.init();
defer direct_allocator.deinit();
var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 300 * 1024);
defer direct_allocator.allocator.free(plenty_of_memory);
var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory);
var a = &fixed_buffer_allocator.allocator;
var context = TestContext{ .data = 0 };
if (builtin.single_threaded) {
TestContext.worker(&context);
expect(context.data == TestContext.incr_count);
} else {
const thread_count = 10;
var threads: [thread_count]*std.os.Thread = undefined;
for (threads) |*t| {
t.* = try std.os.spawnThread(&context, TestContext.worker);
}
for (threads) |t|
t.wait();
expect(context.data == thread_count * TestContext.incr_count);
}
}