Atomics困境:它甚至可以在任何可用的情况下取代互斥锁吗?



我在GNU编译嵌入式系统上使用C11原子(基本上与std::atomics相同)。我很难找到它们的用例,即使是在我正在处理的一个非常简单的示例中。比较下面这个非常简单的程序设计:我希望threadA总是写,而threadB总是读存储在共享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
}

这种方法已经存在各种问题:

  1. 显然,当threadA迭代时,它不会释放先前用于存储字符串的内存。
  2. 我在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获得了字符串的本地副本(这样它就可以随心所欲地处理它),但原子操作仍然可能相互干扰:

  1. 在线程a释放和重新分配内存之间,可以从线程b调用cpy_that_char()并遇到释放的内存。
  2. 在函数cpy_that char中,strnlen()在原子加载后调用时已经不能保证遇到相同的shared_ptr_to_str。线程a ()
  3. 可以释放内存地址。

这意味着我必须将调用分组在一起:free()应该与threadA中的store分组,load应该与threadB中的cpy_that_char()中的strnlen()分组。

这基本上意味着我们回到了互斥体…

几乎在任何情况下,我都会遇到这样的情况。乍一看好像可以用原子来解决,但我一次又一次地回到互斥体。谁能告诉我原子的真实用例是什么,以及我是否可以用原子解决上面的例子?

原子对于原语很有用。例如,如果线程A正在处理项目,线程B正在报告进度,那么线程A可以将处理的项目数写入原子整数,线程B可以在不需要互斥锁的情况下读取它。

如果有多个写入器指向同一个值,它们也很有用。在上面的例子中,进度计数器可以从多个处理线程中自动增加。

另一个用例是"退出"标志,从主线程写入,并定期从工作线程读取,以检查它们是否应该退出。

在您的情况下,如果数据不是原语,那么使用互斥锁是没有问题的。我怀疑,任何只使用原子来编写它的尝试,最终都会重新发明一个互斥锁。

最新更新