原子困境:它甚至可以在任何可用情况下取代互斥量吗?
Atomics dilemma: Can it even replace mutexes in any usable case?
我在 GNU 编译嵌入式系统上使用 C11 原子(与 std::atomics 基本相同)。即使在我正在处理的一个非常简单的示例中,我也无法找到它们的用例。比较以下非常简单的程序设计:我希望线程 A 始终写入而线程 B 读取存储在共享 C 字符串中的信息。
第一次尝试:
#include <string.h>
#include <cstdlib>
#define CONFIGURED_MAX 128
static char* shared_ptr_to_str;
char* get_that_char()
{
char* str = NULL;
__atomic_load(shared_ptr_to_str, str, __ATOMIC_ACQUIRE);
return str;
}
void threadA()
{
char some[] = "Some String we got over network somehow";
char * tmp_ptr = (char*) malloc(CONFIGURED_MAX);
strncpy(tmp_ptr, some, CONFIGURED_MAX);
__atomic_store(shared_ptr_to_str, tmp_ptr, __ATOMIC_RELEASE);
}
void threadB()
{
char* grabbed_str = get_that_char();
// use grabbed_str somehow
}
这种方法已经存在各种问题:
- 显然它不会释放之前在线程 A 迭代时用于存储字符串的内存。
- 我在 threadB() 中保留一个字符串,它可能随时更改。
当我尝试解决这些问题时,我们遇到了情况 B
第二次尝试:
#include <string.h>
#include <cstdlib>
#define CONFIGURED_MAX 128
static char* shared_ptr_to_str;
void cpy_that_char(char** to_fill)
{
char *str = NULL;
__atomic_load(shared_ptr_to_str, str, __ATOMIC_ACQUIRE);
*to_fill = (char*) malloc(strnlen(str, CONFIGURED_MAX));
strncpy(*to_fill, str, CONFIGURED_MAX);
}
void threadA()
{
char some[] = "Some String we got over network somehow";
// free old buffer first
free(shared_ptr_to_str);
// fill in new stuff
char * tmp_ptr = (char*) malloc(strnlen(some, CONFIGURED_MAX));
strncpy(tmp_ptr, some, strnlen(some, CONFIGURED_MAX));
__atomic_store(shared_ptr_to_str, tmp_ptr, __ATOMIC_RELEASE);
}
void threadB()
{
char* grabbed_str = NULL;
cpy_that_char(&grabbed_str);
// use grabbed_str somehow
}
现在我把它弄得更糟了!虽然我现在得到了 threadB 的字符串的本地副本(因此它可以随心所欲地处理它),原子操作仍然会相互干扰:
- 在线程 A 中,在释放和重新分配内存之间,可以从线程 B 调用 cpy_that_char() 并遇到释放的内存。
- 在函数 cpy_that char 中,已经不再保证 strnlen() 在原子加载后调用时会遇到相同的 shared_ptr_to_str。内存地址可能已被 threadA()
释放
这意味着我必须将调用组合在一起:free() 应与 threadA 中的存储分组,加载应与 threadB 的 cpy_that_char() 中的 strnlen() 分组。
这基本上意味着我们又回到了互斥...
几乎在任何情况下我都遇到过这样的情况。乍一看好像可以用原子来解决,但我一次又一次地回到互斥体。谁能告诉我原子的真正用例是什么,我是否可以用原子解决上面的例子?
原子对基元很有用。例如,如果线程 A 正在处理项目,而线程 B 正在报告进度,那么线程 A 可以将处理的项目数写成原子整数,线程 B 无需互斥锁即可读取它。
如果同一值有多个编写器,它们也很有用。在上面的示例中,进度计数器可以从多个处理线程自动递增。
另一个用例是“退出”标志,从主线程写入并定期从工作线程读取以检查它们是否应该退出。
在您的情况下,数据不是原始数据,使用互斥体没有任何问题。我怀疑任何仅使用原子编写它的尝试最终都会重新发明一个互斥体。
我在 GNU 编译嵌入式系统上使用 C11 原子(与 std::atomics 基本相同)。即使在我正在处理的一个非常简单的示例中,我也无法找到它们的用例。比较以下非常简单的程序设计:我希望线程 A 始终写入而线程 B 读取存储在共享 C 字符串中的信息。
第一次尝试:
#include <string.h>
#include <cstdlib>
#define CONFIGURED_MAX 128
static char* shared_ptr_to_str;
char* get_that_char()
{
char* str = NULL;
__atomic_load(shared_ptr_to_str, str, __ATOMIC_ACQUIRE);
return str;
}
void threadA()
{
char some[] = "Some String we got over network somehow";
char * tmp_ptr = (char*) malloc(CONFIGURED_MAX);
strncpy(tmp_ptr, some, CONFIGURED_MAX);
__atomic_store(shared_ptr_to_str, tmp_ptr, __ATOMIC_RELEASE);
}
void threadB()
{
char* grabbed_str = get_that_char();
// use grabbed_str somehow
}
这种方法已经存在各种问题:
- 显然它不会释放之前在线程 A 迭代时用于存储字符串的内存。
- 我在 threadB() 中保留一个字符串,它可能随时更改。
当我尝试解决这些问题时,我们遇到了情况 B
第二次尝试:
#include <string.h>
#include <cstdlib>
#define CONFIGURED_MAX 128
static char* shared_ptr_to_str;
void cpy_that_char(char** to_fill)
{
char *str = NULL;
__atomic_load(shared_ptr_to_str, str, __ATOMIC_ACQUIRE);
*to_fill = (char*) malloc(strnlen(str, CONFIGURED_MAX));
strncpy(*to_fill, str, CONFIGURED_MAX);
}
void threadA()
{
char some[] = "Some String we got over network somehow";
// free old buffer first
free(shared_ptr_to_str);
// fill in new stuff
char * tmp_ptr = (char*) malloc(strnlen(some, CONFIGURED_MAX));
strncpy(tmp_ptr, some, strnlen(some, CONFIGURED_MAX));
__atomic_store(shared_ptr_to_str, tmp_ptr, __ATOMIC_RELEASE);
}
void threadB()
{
char* grabbed_str = NULL;
cpy_that_char(&grabbed_str);
// use grabbed_str somehow
}
现在我把它弄得更糟了!虽然我现在得到了 threadB 的字符串的本地副本(因此它可以随心所欲地处理它),原子操作仍然会相互干扰:
- 在线程 A 中,在释放和重新分配内存之间,可以从线程 B 调用 cpy_that_char() 并遇到释放的内存。
- 在函数 cpy_that char 中,已经不再保证 strnlen() 在原子加载后调用时会遇到相同的 shared_ptr_to_str。内存地址可能已被 threadA() 释放
这意味着我必须将调用组合在一起:free() 应与 threadA 中的存储分组,加载应与 threadB 的 cpy_that_char() 中的 strnlen() 分组。
这基本上意味着我们又回到了互斥...
几乎在任何情况下我都遇到过这样的情况。乍一看好像可以用原子来解决,但我一次又一次地回到互斥体。谁能告诉我原子的真正用例是什么,我是否可以用原子解决上面的例子?
原子对基元很有用。例如,如果线程 A 正在处理项目,而线程 B 正在报告进度,那么线程 A 可以将处理的项目数写成原子整数,线程 B 无需互斥锁即可读取它。
如果同一值有多个编写器,它们也很有用。在上面的示例中,进度计数器可以从多个处理线程自动递增。
另一个用例是“退出”标志,从主线程写入并定期从工作线程读取以检查它们是否应该退出。
在您的情况下,数据不是原始数据,使用互斥体没有任何问题。我怀疑任何仅使用原子编写它的尝试最终都会重新发明一个互斥体。