C中的并发变量访问
我有一个关于 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 字段声明为易失性就足够了,还是我需要在这里做其他事情? 还有更好的方法来处理这个问题吗?
I have a fairly specific question about concurrent programming in C. I have done a fair bit of research on this but have seen several conflicting answers, so I'm hoping for some clarification. I have a program that's something like the following (sorry for the longish code block):
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. */
}
There is a pool of threads servicing the connection for incoming data, so multiple instances of newData can be running at the same time. In a multi-processor environment there are two problems I'm aware of in getting the counter handling part of this code correct: preventing the compiler from caching the shared counter copy in a register so other threads can't see it, and forcing the CPU to write the store of the counter value to memory in a timely fashion so other threads can see it. I would prefer not to use a synchronization call around the counter checks because a partial read of the counter value is acceptable (it will produce a value different than the local copy, which should be adequate to conclude that an event has occurred). Would it be sufficient to declare the eventCounter field in SharedData to be volatile, or do I need to do something else here? Also is there a better way to handle this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
不幸的是,C 标准对并发性的描述很少。 然而,大多数编译器(无论如何,gcc 和 msvc)都会将易失性读取视为具有 获取语义——每次访问时都会从内存中重新加载易失性变量。 这是可取的,您现在的代码可能最终会比较寄存器中缓存的值。 如果这两个比较都得到优化,我什至不会感到惊讶。
所以答案是肯定的,让
eventCounter
变得易失。 或者,如果您不想过多限制编译器,可以使用以下函数来执行eventCounter
的读取。Unfortunately, the C standard says very little about concurrency. However, most compilers (gcc and msvc, anyway) will regard a volatile read as if having acquire semantics -- the volatile variable will be reloaded from memory on every access. That is desirable, your code as it is now may end up comparing values cached in registers. I wouldn't even be surprised if the both comparisons were optimized out.
So the answer is yes, make the
eventCounter
volatile. Alternatively, if you don't want to restrict your compiler too much, you can use the following function to perform reads ofeventCounter
.您的本地计数器副本是“本地”的,在执行堆栈上创建,并且仅对正在运行的线程可见。 每个其他线程都在不同的堆栈中运行,并具有自己的本地计数器变量(无并发)。
您的全局计数器应声明为易失性以避免寄存器优化。
Your local counter copy is "local", created on the execution stack and visible only to the running thread. Every other thread runs in a different stack and has the own local counter variable (no concurrency).
Your global counter should be declared volatile to avoid register optimization.
您还可以使用手动编码的程序集或编译器内在函数,它将保证原子检查你的互斥锁,它们也可以原子地 ++ 和 - 你的计数器。
易失性是 无用,您应该查看内存屏障,它们是其他低级 CPU 设施,可帮助解决多核争用问题。
然而,我能给的最好建议是让您深入了解各种托管和本机多核支持库。 我猜一些较旧的,如 OpenMP 或 MPI(基于消息),仍然很受欢迎,人们会继续说它们有多酷......但是对于大多数开发人员来说,像英特尔的 TBB 或 微软的新API,我也刚刚挖了这个代码项目文章< /a>,他显然正在使用 cmpxchg8b,这是我最初提到的低级硬件路线......
祝你好运。
You can also use hand coded assembly or compiler intrinsics which will garuntee atomic checks against your mutex, they can also atomically ++ and -- your counter.
volatile is useless these days, for the most part, you should look at memory barrier's which are other low level CPU facility to help with multi-core contention.
However the best advice I can give, would be for you to bone up on the various managed and native multi-core support libraries. I guess some of the older one's like OpenMP or MPI (message based), are still kicking and people will go on about how cool they are... however for most developers, something like intel's TBB or Microsoft's new API's, I also just dug up this code project article, he's apparently using cmpxchg8b which is the lowlevel hardware route which I mentioned initially...
Good luck.