假设我有一些工作线程如下:
while (1) {
do_something();
if (flag_isset())
do_something_else();
}
我们有几个辅助函数用于检查和设置标志:
void flag_set() { global_flag = 1; }
void flag_clear() { global_flag = 0; }
int flag_isset() { return global_flag; }
因此,线程在繁忙循环中不断调用do_something()
,如果其他线程设置了global_flag
,线程也会调用do_something_else()
(例如当请求通过从另一个线程设置标志来输出进度或调试信息时)。我的问题是:我需要做一些特殊的事情来同步访问global_flag吗?如果是,那么在可移植性方面,最小化同步工作要做什么? 我尝试通过阅读许多文章来弄清楚这个问题,但我仍然不确定正确答案是什么...我认为以下两种情况之一是正确的:
A:无需同步,因为设置或清除标志不会创建竞争条件:
我们只需要将该标志定义为volatile
,以确保每次检查时它都真正地从共享内存中读取:volatile int global_flag;
可能不会立即传播到其他CPU核心,但肯定迟早会传播。
B: 为确保标志的更改在线程之间传播,需要进行完全同步:
在一个CPU核心中设置共享标志并不一定能被另一个核心看到。我们需要使用互斥锁来确保通过无效化其他CPU上的相应缓存行始终传播标志更改。代码如下:
volatile int global_flag;
pthread_mutex_t flag_mutex;
void flag_set() { pthread_mutex_lock(flag_mutex); global_flag = 1; pthread_mutex_unlock(flag_mutex); }
void flag_clear() { pthread_mutex_lock(flag_mutex); global_flag = 0; pthread_mutex_unlock(flag_mutex); }
int flag_isset()
{
int rc;
pthread_mutex_lock(flag_mutex);
rc = global_flag;
pthread_mutex_unlock(flag_mutex);
return rc;
}
C:需要同步以确保标志的更改在线程之间传播:
这与B相同,但不是在读取端和写入端都使用互斥量,而是仅在写入端设置。因为逻辑不需要同步。我们只需要在更改标志时同步(使其他缓存失效):
volatile int global_flag;
pthread_mutex_t flag_mutex;
void flag_set() { pthread_mutex_lock(flag_mutex); global_flag = 1; pthread_mutex_unlock(flag_mutex); }
void flag_clear() { pthread_mutex_lock(flag_mutex); global_flag = 0; pthread_mutex_unlock(flag_mutex); }
int flag_isset() { return global_flag; }
当我们知道标志很少更改时,这将避免不断地锁定和解锁互斥体。我们只是利用了Pthreads互斥体的一个副作用,以确保更改被传播。
那么,哪一个?
我认为A和B是明显的选择,B更安全。但C呢?
如果C可以,是否有其他方法强制在所有CPU上都可见标志更改?
还有一个相关的问题:使用pthread互斥体保护变量是否保证它也不会被缓存?…但这并没有真正回答这个问题。
sig_atomic_t
声明变量,以确保可以在一条指令中完成get/set操作。 - Michael Miorsig_atomic_t
使变量的访问具有原子性(这并不是这个问题的重点),但这是否保证了缓存一致性?或者说,首先是否存在任何需要担心的缓存一致性问题? - snap