C Passing struct to a function between threads
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Passing a struct to work running on another thread is mostly a question of ownership and lifetime, not syntax. The real decision is whether the thread should receive its own copy, share immutable data, or share mutable data protected by synchronization.
Copying Is the Safest Default
If the struct is reasonably small or copying is acceptable, pass it by value to the thread function. Each thread gets its own independent object and there is no shared-state race.
That call copies job into the thread's argument list. The worker can modify its local copy without affecting the original.
This is usually the best starting point because it eliminates a large class of threading bugs.
Sharing a Struct by Reference
Sometimes the worker must operate on the original struct instead of a copy. In C++, that means passing a reference with std::ref.
Without std::ref, the thread would receive a copy instead.
But once you share the original object, you also take responsibility for synchronization if more than one thread may touch it.
Shared Mutable Data Needs Synchronization
If multiple threads can read and write the same struct, you need a mutex, atomics, or another synchronization primitive. Otherwise you have a data race.
The lock is not optional here. Without it, incrementing processed from multiple threads is undefined behavior.
Lifetime Is the Most Common Hidden Bug
A thread must never outlive the data it uses. This is where many examples go wrong. Consider this bad pattern:
local is destroyed when startThread returns. The new thread now holds a dangling reference.
If the worker needs shared access beyond the caller's stack frame, use one of these instead:
- copy the struct into the thread
- allocate shared lifetime explicitly with
std::shared_ptr - keep the owner alive until the thread completes
std::shared_ptr for Shared Ownership
When several threads legitimately need access to the same heap object, std::shared_ptr can make lifetime management clearer.
This solves lifetime, but it does not solve shared mutable access automatically. If the object is mutable and shared, you still need synchronization.
POSIX Threads Use the Same Rules
If you are writing C with pthread_create, the mechanics look different, but the rules are identical: the pointed-to struct must remain alive, and shared mutation needs synchronization.
The syntax changes, but the ownership and race concerns do not.
Common Pitfalls
A common mistake is passing a reference or pointer to a local struct that goes out of scope before the thread finishes.
Another mistake is assuming that because the code compiled, sharing mutable fields is safe. It is not safe without synchronization.
Developers also sometimes optimize too early by avoiding copies when a simple value copy would have been the safest design.
Finally, std::shared_ptr solves lifetime only. It does not make concurrent mutation safe by itself.
Summary
- Copy the struct into the thread when possible. It is the safest default.
- Use
std::refonly when the thread must work on the original object. - Protect shared mutable structs with a mutex or another synchronization mechanism.
- Make sure any referenced data outlives the thread that uses it.
- Separate lifetime management from synchronization in your design. They are related, but they solve different problems.

