【问题标题】:concurrent variable access in cc中的并发变量访问
【发布时间】:2009-05-16 06:50:53
【问题描述】:

我有一个关于 C 并发编程的相当具体的问题。我对此进行了相当多的研究,但看到了几个相互矛盾的答案,所以我希望得到一些澄清。我有一个类似于以下的程序(抱歉,代码块过长):

typedef struct {
  pthread_mutex_t mutex;
  /* some shared data */
  int eventCounter;
} SharedData;

SharedData globalSharedData;

typedef struct {
  /* details unimportant */
} NewData;

void newData(NewData data) {
  int localCopyOfCounter;

  if (/* information contained in new data triggers an
         event */) {
    pthread_mutex_lock(&globalSharedData.mutex);
    localCopyOfCounter = ++globalSharedData.eventCounter;
    pthread_mutex_unlock(&globalSharedData.mutex);
  }
  else {
    return;
  }

  /* Perform long running computation. */

  if (localCopyOfCounter != globalSharedData.eventCounter) {
    /* A new event has happened, old information is stale and
       the current computation can be aborted. */
    return;
  }

  /* Perform another long running computation whose results
     depend on the previous one. */

  if (localCopyOfCounter != globalSharedData.eventCounter) {
    /* Another check for new event that causes information
       to be stale. */
    return;
  }

  /* Final stage of computation whose results depend on two
     previous stages. */
}

有一个线程池为传入数据的连接提供服务,因此可以同时运行多个 newData 实例。在多处理器环境中,我知道在正确处理此代码的计数器部分时存在两个问题:阻止编译器将共享计数器副本缓存在寄存器中,以便其他线程看不到它,并强制CPU 及时将计数器值的存储写入内存,以便其他线程可以看到它。我不希望在计数器检查周围使用同步调用,因为可以接受部分读取计数器值(它将产生与本地副本不同的值,这应该足以得出事件已发生的结论)。将 SharedData 中的 eventCounter 字段声明为 volatile 就足够了,还是我需要在这里做其他事情?还有没有更好的方法来处理这个问题?

【问题讨论】:

    标签: c multithreading concurrency multicore


    【解决方案1】:

    不幸的是,C 标准很少提及并发性。但是,大多数编译器(无论如何,gcc 和 msvc)都会将 volatile 读取视为具有 acquire semantics —— volatile 变量将在每次访问时从内存中重新加载。这是可取的,您现在的代码可能最终会比较缓存在寄存器中的值。如果这两个比较都被优化了,我什至不会感到惊讶。

    所以答案是肯定的,让eventCounter 易变。或者,如果您不想过多地限制编译器,可以使用以下函数来执行 eventCounter 的读取。

    int load_acquire(volatile int * counter) { return *counter; }
    
    if (localCopy != load_acquire(&sharedCopy))
        // ...
    

    【讨论】:

      【解决方案2】:

      防止编译器缓存 寄存器中的本地计数器副本 所以其他线程看不到它

      您的本地计数器副本是“本地的”,在执行堆栈上创建并且仅对正在运行的线程可见。每个其他线程都在不同的堆栈中运行,并且有自己的本地计数器变量(无并发)。

      您的全局计数器应声明为 volatile 以避免寄存器优化。

      【讨论】:

      • 您对本地副本的看法是绝对正确的,我刚刚在我的文本中修复了该错误。
      【解决方案3】:

      您还可以使用手动编码的程序集或编译器intrinsics,这将保证对您的互斥体进行原子检查,它们也可以原子地 ++ 和 -- 您的计数器。

      现在 volatile 是 useless,在大多数情况下,您应该查看内存屏障,它们是其他低级 CPU 工具,可以帮助解决多核争用问题。

      不过,我能给出的最佳建议是让您了解各种托管和原生多核支持库。我猜一些像 OpenMP 或 MPI(基于消息的)这样的老的,仍然在踢,人们会继续谈论他们有多酷......但是对于大多数开发人员来说,像英特尔的 TBBMicrosoft's 的新 API,我也刚刚挖出了这个代码项目article,他显然在使用cmpxchg8b,这是我最初提到的低级硬件路线......

      祝你好运。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-04-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多