# When to use mutex or atomic

**URL:** <https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150>\
**Category:** Explain\
**Created:** [August 9, 2026, 6:25am UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150 "2026-08-09T06:25:04Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [August 9, 2026, 6:25am UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/1 "2026-08-09T06:25:04Z")

</div>

My problem:

1. I’ve got a field of a struct and I need to access the data across multiple `io.async` and `io.concurrent`.
2. The data is small (\< 8 bits)
3. I need to read / write from thread 1 and read / write from thread 2.
4. I do not need to protect it from reads/writes for extended periods, I only need to prevent it being read or written as corrupted.

Questions:

1. When should I protect the data with a mutex vs `std.atomic.Value()`?
2. Are there code portability concerns between using mutex vs atomic? Which is more portable?
3. If something was different about my problem, what would encourage using mutex?
4. If something was different about my problem, what would encourage using atomic?
5. Why is this there no `std.atomic.Value()` equivalent in `std.Io`?

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [August 9, 2026, 6:34am UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/2 "2026-08-09T06:34:52Z")

</div>

1. Use mutex when you need to coordinate multiple values, or the values are larger than the maximum atomic size, or the critical sections are more complex than a simple atomic swap/incr/whatever.
2. Both are portable, this is not an issue.
3. If your value is larger than atomic size on your platform, you obviously need a mutex. There are also special problems, where you have read-heavy workload, and then some more complex write operation and you have concurrent writers, it might actually make sense to use both mutex and atomic. Atomic load on the read path, mutex + atomic write on the write path. Generally, under really heavy contention, mutexes do better. It’s counter-intuitive, because the promise of lock free is that it always advances, but CPUs still need to lock cachelines, etc.
4. You already should use atomic. 🙂
5. Atomic is not an I/O concern. Mutex is in `std.Io` because it has to park the current task while it waits.

---

<div class="post-metadata">

**Author:** ![Bobvan](https://ziggit.dev/user_avatar/ziggit.dev/bobvan/32/5105_2.png) [@Bobvan](https://ziggit.dev/u/Bobvan)\
**Post date:** [August 9, 2026, 8:52am UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/3 "2026-08-09T08:52:17Z")

</div>

Just adding onto @lalinsky’s great response, look into a concept called “false sharing”.  
Since you are dealing with really small shared values, it may be that you have the thread-1 and thread-2 specific data next to each other in memory. I know you didn’t really ask for performance differences, but if you are interested this could be much more important to speed of your application than Mutex vs Atomic choice.

Robert 😊

---

<div class="post-metadata">

**Author:** ![Kaeru](https://ziggit.dev/user_avatar/ziggit.dev/kaeru/32/3335_2.png) [@Kaeru](https://ziggit.dev/u/Kaeru)\
**Post date:** [August 9, 2026, 8:59am UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/4 "2026-08-09T08:59:42Z")

</div>

I agree that atomics are the correct choice contextually. @lalinsky answer sums it up pretty well but I’d also add onto it that even when an atomic’s size would be enough there’s still a situation where you would want to use a Mutex.

Atomics by themselves don’t guarantee “mutual exclusivity” in the same way mutexes do. They guarantee order of operations.

So, as you say, if you need to protect this value within a block and you want to make sure no changes happen while you are doing some operations, you should use a mutex.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [August 9, 2026, 9:29am UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/5 "2026-08-09T09:29:08Z")

</div>

Yes, that’s the obvious case for mutexes that i completely forgot 🙂

The canonical example for mutexes:

```zig
try mutex.lock(io);
defer mutex.unlock(io);

if (need_to_do_something) {
    doSomething();
    need_to_do_something = false; 
}

```

With naive atomic load/store instead of plain access, you have a race situation, you need mutual exclusivity.

However, in a simple case like that, there is also a lock-free version with atomics:

```zig
const do_it = need_to_do_something.swap(false, .acq_rel);
if (do_it) {
    doSomething();
}

```

---

<div class="post-metadata">

**Author:** ![vincentd](https://ziggit.dev/user_avatar/ziggit.dev/vincentd/32/9870_2.png) [@vincentd](https://ziggit.dev/u/vincentd)\
**Post date:** [August 9, 2026, 10:46am UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/6 "2026-08-09T10:46:42Z")

</div>

A thing to be aware of is that the std library actually has two different types of Mutex:

- `std.Io.Mutex`
- `std.atomic.Mutex`

They both have the same function; prevent two threads from simultaneously entering a ‘critical section’ of code. `std.atomic.Mutex` is much simpler though, and doesn’t require an Io parameter.

Why then do we use `std.Io.Mutex`?

The problem is that your program runs under an operating system, and the OS can - at any moment - pause a thread and use the CPU to do something else for a while. If that happens in the middle of a critical section, the mutex will remain locked until the OS decides its time to run that thread again, which could take a very long time.

What do the other threads do in the meantime? If you’re using `std.Atomic.Mutex`, they will continue making futile attempts to lock the mutex, completely wasting CPU time until the OS pauses them as well. To make matters worse, there is no guarantee that the thread holding the mutex will start running before the threads that are waiting for it!

`std.Io.Mutex` solves this problem by taking an Io parameter. It uses it to tell the OS that it can’t proceed, and more importantly, _why_ it can’t proceed, by passing pointer to the mutex it’s waiting for. The OS immediately stops running the thread, and doesn’t bother running it again until the thread that holds the mutex unlocks it.

There are situations in which `std.atomic.Mutex` is the better choice, but these are rare. If in doubt, use `std.Io.Mutex`.

---

<div class="post-metadata">

**Author:** ![pzittlau](https://ziggit.dev/letter_avatar_proxy/v4/letter/p/ecae2f/32.png) [@pzittlau](https://ziggit.dev/u/pzittlau)\
**Post date:** [August 9, 2026, 1:40pm UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/8 "2026-08-09T13:40:13Z")

</div>

I honestly don’t know if I should really send the following and do the “uhm well akshually 🤓”… but here I am. I just find that answer so … I can’t really describe it. This is by no means meant to be mean. I also don’t know if it’s just a language thing.

* * *

> [@IceGuye](#):
>
> Atomic means “single”, “smallest”.

Atomic comes from _indivisible_. And this at least to me is much better at explaining it.

> [@IceGuye](#):
>
> Atomic values’ operations are CPU instructions. It’s on hardware level.

Everything on a computer is.

> [@IceGuye](#):
>
> When using these instructions, the CPU guarantees that the operation is single and smallest; it’s **the reality** in CPU world so there is no “mid point” no “seam” that other CPUs can access and interrupt it.

Atomics aren’t “single”. They are, at least for all the microarchitectures I’ve used, implemented in multiple uops(as are most other instructions). See [here](https://uops.info/html-instr/CMPXCHG_LOCK_M64_R64.html) for amd64.

The second point about the “mid point”/“seem” is more correct. Another cpu can’t read a torn state. So if one cpu changes a value from `0x0000000` to `0xffffffff` another one can’t read `0x0000ffff`. This is the meaning of indivisible. It is, to me, somewhat similar to a database transaction but just on a single word.

> [@IceGuye](#):
>
> Mutex is an operating system level thing.

`mutex` means mutual exclusion. This is also mutex:

```zig
const SpinLock = @This();
is_locked: atomic.Value(bool),
pub const init: SpinLock = .{ .is_locked = .init(false) };
pub fn lock(self: *SpinLock) void {
    while (self.is_locked.load(.unordered) or
        self.is_locked.cmpxchgWeak(false, true, .acq_rel, .monotonic) != null)
    {
        @branchHint(.unlikely);
        atomic.spinLoopHint();
    }
}

pub fn unlock(self: *SpinLock) void {
    self.is_locked.store(false, .release);
}

```

Operating system integration for mutexes is mostly to avoid busy wait loops and maybe don’t schedule lock holder off the cpu to keep the critical section short.

> [@IceGuye](#):
>
> If you have a function and struct that need to operate multiple values or even arrays, slices, you have to go with Mutex.

You don’t have to. Though generally using a mutex is a good starting point. There are various techniques that can be much faster and efficient than a mutex depending on how and when the data is changed; the read vs. write ratio; the length of the critical section…

* * *

Sorry for this post

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [August 9, 2026, 3:24pm UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/9 "2026-08-09T15:24:35Z")

</div>

> [@pzittlau](#):
>
> `self.is_locked.cmpxchgWeak(false, true, .acq_rel, .monotonic) != null)`

Just as a fun fact, `swap(true, .acl_rql)` is the faster option for a spinlock. Was recently watching this video on atomics, really interesting stuff:

[![](https://img.youtube.com/vi/UdKqfQ3a_sY/maxresdefault.jpg "Lock-free Programming is Dead - Long Live Lock-free Programming! - Fedor G Pikus - C++Now 2026") ](https://www.youtube.com/watch?v=UdKqfQ3a_sY)

(Disclaimer: this is super advanced, don’t use spinlocks unless you really know what you are doing. If you are not sure, just use `std.Io.Mutex`, your code will be safe and fast enough.)

---

<div class="post-metadata">

**Author:** ![pzittlau](https://ziggit.dev/letter_avatar_proxy/v4/letter/p/ecae2f/32.png) [@pzittlau](https://ziggit.dev/u/pzittlau)\
**Post date:** [August 9, 2026, 3:39pm UTC](https://ziggit.dev/t/when-to-use-mutex-or-atomic/17150/10 "2026-08-09T15:39:25Z")

</div>

Yeah that talk is quite good.

I was writing the spinlock from memory and couldn’t quite remember if it was `xchg` or `cmpxchg`. Thank for the reminder 😄
