c中的并发变量访问

时间:2009-05-16 06:50:54

标签: c multithreading concurrency multicore

我对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是否足够,或者我是否需要在此处执行其他操作?还有更好的方法来解决这个问题吗?

3 个答案:

答案 0 :(得分:2)

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

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

int load_acquire(volatile int * counter) { return *counter; }

if (localCopy != load_acquire(&sharedCopy))
    // ...

答案 1 :(得分:0)

  

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

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

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

答案 2 :(得分:0)

您还可以使用手动编码汇编或编译器intrinsics,它将对您的互斥锁进行原子检查,它们也可以原子++和 - 您的计数器。

volatile现在是useless,在大多数情况下,您应该查看内存障碍,这是其他低级CPU工具,以帮助进行多核争用。

但是,我能给出的最佳建议是,您可以了解各种托管和本机多核支持库。我想一些较旧的版本如OpenMP或MPI(基于消息),仍然在踢人们会继续讨论它们有多酷......但是对于大多数开发人员来说,像intel TBB或{{3}新的API,我也挖出了这个代码项目Microsoft's,他显然使用的是cmpxchg8b,这是我最初提到的低级硬件路径......

祝你好运。