Date: Fri, 9 Jan 2026 22:29:39 +0000
On Fri, Jan 9, 2026 at 5:25 PM Thiago Macieira wrote:
>
> I skipped the discussion about faking behaviour. That's either disingenuous,
> for debugging only, or matches the behaviour we already have in the
> std::atomic types.
You hit the nail on the head there with those last ten words, "matches
the behaviour we already have in the std::atomic types".
When a beginner programmer starts out learning about multi-threading,
data races, races conditions and so on, they learn about locks (e.g.
mutexes, semaphores) and also about atomic variables.
Atomics are presented as an alternative to locks. That is to say,
'atomic' pretty much means 'lockfree' -- or at least it was supposed
to.
But then someone at some point came along and said "oh hey why don't
we just make std::atomic work for EVERY type, even if we need to hide
a lock inside a global container indexed by memory addresses". This is
absolutely catastrophic -- so catastrophic in fact that we can't place
the cx16 instruction inline in code just in case another translation
unit is using the lack-lustre lock-in-a-global-container way of doing
things. This is so bad. If you want to protect an object with a lock
then do something like:
template<typename T> requires is_object_v<T>
class Multiaccess {
mutex mtx;
T obj;
public:
template<typename... Params>
Multiaccess(Params&&... args) noexcept( noexcept(
T(forward<Params>(args)...) ) )
: obj( forward<Params>(args)... ) {}
auto operator*(void) noexcept // Let program terminate if lock throws
{
return pair< T&, unique_lock<mutex> >{ obj, unique_lock(mtx) };
}
};
Multiaccess<string> mstr("frog");
void Func(void)
{
auto [ s, mylock ] = *mstr;
cout << s << endl;
s = "Hello World!";
}
int main(void)
{
Func();
auto [ s, mylock ] = *mstr;
cout << s << endl;
}
I don't think any programmer worth their salt would use std::atomic
and let the implementation decided whether or not to use a lock (and
also let the implementation use a lock located in a global container
indexed by memory address).
"std::atomic" is ruined in C++ and I think it has to be given up on.
Time to throw the baby out with the bath water. We should clone it and
call it "std::lockfree" -- except that "std::lockfree" isn't allowed
to use secret locks.
Honestly I can do a copy-paste in the GNU g++ compiler repository
tonight . . . I can copy all the files related to 'std::atomic' and
rename everything to 'std::lockfree', and then I can rip out the
hidden lock stuff.
So then in the future programmers will have three choices:
(1) Use your own mutex to lock a lock before accessing an object
(2) Use std::lockfree to avoid locks (and get a compile time error
if it's not possible)
(3) Use std::atomic to let the compiler decide
Yeah I definitely think that we need "std::lockfree" in the C++
Standard as a clone of "std::atomic" except that it's forbidden to use
a lock.
>
> I skipped the discussion about faking behaviour. That's either disingenuous,
> for debugging only, or matches the behaviour we already have in the
> std::atomic types.
You hit the nail on the head there with those last ten words, "matches
the behaviour we already have in the std::atomic types".
When a beginner programmer starts out learning about multi-threading,
data races, races conditions and so on, they learn about locks (e.g.
mutexes, semaphores) and also about atomic variables.
Atomics are presented as an alternative to locks. That is to say,
'atomic' pretty much means 'lockfree' -- or at least it was supposed
to.
But then someone at some point came along and said "oh hey why don't
we just make std::atomic work for EVERY type, even if we need to hide
a lock inside a global container indexed by memory addresses". This is
absolutely catastrophic -- so catastrophic in fact that we can't place
the cx16 instruction inline in code just in case another translation
unit is using the lack-lustre lock-in-a-global-container way of doing
things. This is so bad. If you want to protect an object with a lock
then do something like:
template<typename T> requires is_object_v<T>
class Multiaccess {
mutex mtx;
T obj;
public:
template<typename... Params>
Multiaccess(Params&&... args) noexcept( noexcept(
T(forward<Params>(args)...) ) )
: obj( forward<Params>(args)... ) {}
auto operator*(void) noexcept // Let program terminate if lock throws
{
return pair< T&, unique_lock<mutex> >{ obj, unique_lock(mtx) };
}
};
Multiaccess<string> mstr("frog");
void Func(void)
{
auto [ s, mylock ] = *mstr;
cout << s << endl;
s = "Hello World!";
}
int main(void)
{
Func();
auto [ s, mylock ] = *mstr;
cout << s << endl;
}
I don't think any programmer worth their salt would use std::atomic
and let the implementation decided whether or not to use a lock (and
also let the implementation use a lock located in a global container
indexed by memory address).
"std::atomic" is ruined in C++ and I think it has to be given up on.
Time to throw the baby out with the bath water. We should clone it and
call it "std::lockfree" -- except that "std::lockfree" isn't allowed
to use secret locks.
Honestly I can do a copy-paste in the GNU g++ compiler repository
tonight . . . I can copy all the files related to 'std::atomic' and
rename everything to 'std::lockfree', and then I can rip out the
hidden lock stuff.
So then in the future programmers will have three choices:
(1) Use your own mutex to lock a lock before accessing an object
(2) Use std::lockfree to avoid locks (and get a compile time error
if it's not possible)
(3) Use std::atomic to let the compiler decide
Yeah I definitely think that we need "std::lockfree" in the C++
Standard as a clone of "std::atomic" except that it's forbidden to use
a lock.
Received on 2026-01-09 22:28:47
