原子困境:它甚至可以在任何可用情况下取代互斥量吗?

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
}

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

  1. 显然它不会释放之前在线程 A 迭代时用于存储字符串的内存。
  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。内存地址可能已被 threadA()
  3. 释放

这意味着我必须将调用组合在一起:free() 应与 threadA 中的存储分组,加载应与 threadB 的 cpy_that_char() 中的 strnlen() 分组。

这基本上意味着我们又回到了互斥...

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

原子对基元很有用。例如,如果线程 A 正在处理项目,而线程 B 正在报告进度,那么线程 A 可以将处理的项目数写成原子整数,线程 B 无需互斥锁即可读取它。

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

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

在您的情况下,数据不是原始数据,使用互斥体没有任何问题。我怀疑任何仅使用原子编写它的尝试最终都会重新发明一个互斥体。