我可以在多线程情况下使用不带锁的char变量吗
Can I use char variable without lock in the multi-threading case
正如c/c++标准所说,char
的大小必须是1
。据我了解,这意味着 CPU 保证对 char
的任何读取或写入都必须在一条指令中完成。
假设我们有很多线程,它们共享一个 char
变量:
char target = 1;
// thread a
target = 0;
// thread b
target = 1;
// thread 1
while (target == 1) {
// do something
}
// thread 2
while (target == 1) {
// do something
}
一句话,线程有两种:一种是把target
设置成0
或者1
,另一种是做一些任务如果target == 1
。目标是我们可以通过修改 target
.
的值来控制任务线程
据我了解,似乎根本不需要使用mutex/lock
。但是我的编码经验给了我一个强烈的感觉,在这种情况下我们必须使用mutex/lock
。
我现在很困惑。在这种情况下我应该使用 mutex/lock
还是不使用?
你看,我能理解为什么我们在其他情况下需要mutex/lock
,比如i++
。因为 i++
不能只用一条指令完成。那么 target = 0
可以在一条指令中完成吗?如果是这样,是否意味着在这种情况下我们不需要 mutex/lock
?
嗯,我知道我们可以使用 std::atomic
,所以我的问题是:既不使用 mutex/lcok
也不使用 std::atomic
是否可以。
std::atomic
保证访问变量是原子的。来自 cppreference:
Each instantiation and full specialization of the std::atomic template
defines an atomic type. If one thread writes to an atomic object while
another thread reads from it, the behavior is well-defined (see memory
model for details on data races).
当 char
实际上是原子的(大小为 1 是不够的),那么 std::atomic<char>
不需要额外的同步。但是,在 char
不是原子的平台上,std::atomic<char>
保证可以通过使用互斥锁或类似的方式以原子方式读取和写入它。
实际上,我希望 char
是原子的,但标准并不能保证这一点。
还要考虑像 +=
读取 和 写入值这样的操作,因此单独的原子读取和写入不足以安全地调用 +=
,而 std::atomic<T>
有一个合适的 operator+=
.
TL;DR
I'm confused now. Should I use mutex/lock or not in this case?
让其他人替您做决定。当你想要原子的东西时,使用 std::atomic<something>
除非你想要对同步进行细粒度控制。
TL;DR
is it OK to not use neither mutex/lcok nor std::atomic
没有
一般来说,假设事情是不好的。如果您需要某些保证,请确保您有保证。
这与一个常见的逻辑谬误密切相关。仅仅因为你无法想象为什么某件事可能是真的,并不意味着它就是真的。
更长的版本
As c/c++ standard said
没有“C/C++”这样的东西,绝对不是“C/C++标准”。它们是两种完全不同的语言,具有不同的标准。但是,他们确实同意这一点。 sizeof (char)
在两种语言中都是 1。
(旁注:sizeof 'a'
会产生不同的结果。)
As my understanding, that means CPU guarantees that any read or write on a char must be done in one instruction.
这是不正确的。 CPU 有自己的规范,完全独立于语言标准。并没有说这必须是真的,即使它可能在大多数或所有情况下都是如此。
Because i++ can't be done in only one instruction.
即CPU依赖。 x86 架构对此有一个说明。 https://c9x.me/x86/html/file_module_x86_id_140.html
As my understanding, it doesn't seem that we need to use mutex/lock at all. But my coding experience gave me a strong feeling that we must use mutex/lock in this case.
即使目标 CPU 确实在一条指令中读取和写入(它可能确实如此),也没有任何内容表明 C 或 C++ 代码需要编译为该指令。
C 和 C++ 的标准描述了代码的行为。不是它如何转换为程序集。
所以不,你不能做出你正在做的假设。
一般来说,不能假设读取或写入char
是一个原子操作。但是,目标架构 可能 提供这种保证。对于嵌入式 C 程序,通常的做法是依赖此类底层保证来避免在某些情况下同步机制的开销。
在问题的例子中必须注意,即使 reading/writing target
是一个原子操作,该值也可以随时更改,因此不能保证它会在 while 循环中 1
。
正如c/c++标准所说,char
的大小必须是1
。据我了解,这意味着 CPU 保证对 char
的任何读取或写入都必须在一条指令中完成。
假设我们有很多线程,它们共享一个 char
变量:
char target = 1;
// thread a
target = 0;
// thread b
target = 1;
// thread 1
while (target == 1) {
// do something
}
// thread 2
while (target == 1) {
// do something
}
一句话,线程有两种:一种是把target
设置成0
或者1
,另一种是做一些任务如果target == 1
。目标是我们可以通过修改 target
.
据我了解,似乎根本不需要使用mutex/lock
。但是我的编码经验给了我一个强烈的感觉,在这种情况下我们必须使用mutex/lock
。
我现在很困惑。在这种情况下我应该使用 mutex/lock
还是不使用?
你看,我能理解为什么我们在其他情况下需要mutex/lock
,比如i++
。因为 i++
不能只用一条指令完成。那么 target = 0
可以在一条指令中完成吗?如果是这样,是否意味着在这种情况下我们不需要 mutex/lock
?
嗯,我知道我们可以使用 std::atomic
,所以我的问题是:既不使用 mutex/lcok
也不使用 std::atomic
是否可以。
std::atomic
保证访问变量是原子的。来自 cppreference:
Each instantiation and full specialization of the std::atomic template defines an atomic type. If one thread writes to an atomic object while another thread reads from it, the behavior is well-defined (see memory model for details on data races).
当 char
实际上是原子的(大小为 1 是不够的),那么 std::atomic<char>
不需要额外的同步。但是,在 char
不是原子的平台上,std::atomic<char>
保证可以通过使用互斥锁或类似的方式以原子方式读取和写入它。
实际上,我希望 char
是原子的,但标准并不能保证这一点。
还要考虑像 +=
读取 和 写入值这样的操作,因此单独的原子读取和写入不足以安全地调用 +=
,而 std::atomic<T>
有一个合适的 operator+=
.
TL;DR
I'm confused now. Should I use mutex/lock or not in this case?
让其他人替您做决定。当你想要原子的东西时,使用 std::atomic<something>
除非你想要对同步进行细粒度控制。
TL;DR
is it OK to not use neither mutex/lcok nor std::atomic
没有
一般来说,假设事情是不好的。如果您需要某些保证,请确保您有保证。
这与一个常见的逻辑谬误密切相关。仅仅因为你无法想象为什么某件事可能是真的,并不意味着它就是真的。
更长的版本
As c/c++ standard said
没有“C/C++”这样的东西,绝对不是“C/C++标准”。它们是两种完全不同的语言,具有不同的标准。但是,他们确实同意这一点。 sizeof (char)
在两种语言中都是 1。
(旁注:sizeof 'a'
会产生不同的结果。)
As my understanding, that means CPU guarantees that any read or write on a char must be done in one instruction.
这是不正确的。 CPU 有自己的规范,完全独立于语言标准。并没有说这必须是真的,即使它可能在大多数或所有情况下都是如此。
Because i++ can't be done in only one instruction.
即CPU依赖。 x86 架构对此有一个说明。 https://c9x.me/x86/html/file_module_x86_id_140.html
As my understanding, it doesn't seem that we need to use mutex/lock at all. But my coding experience gave me a strong feeling that we must use mutex/lock in this case.
即使目标 CPU 确实在一条指令中读取和写入(它可能确实如此),也没有任何内容表明 C 或 C++ 代码需要编译为该指令。
C 和 C++ 的标准描述了代码的行为。不是它如何转换为程序集。
所以不,你不能做出你正在做的假设。
一般来说,不能假设读取或写入char
是一个原子操作。但是,目标架构 可能 提供这种保证。对于嵌入式 C 程序,通常的做法是依赖此类底层保证来避免在某些情况下同步机制的开销。
在问题的例子中必须注意,即使 reading/writing target
是一个原子操作,该值也可以随时更改,因此不能保证它会在 while 循环中 1
。