我和同事为在x86,x64,Itanium,PowerPC和其他10年历史的服务器CPU上运行的各种平台编写软件.
我们刚刚讨论了pthread_mutex_lock()... pthread_mutex_unlock()等互斥函数本身是否足够,或者受保护变量是否需要是volatile.
int foo::bar()
{
//...
//code which may or may not access _protected.
pthread_mutex_lock(m);
int ret = _protected;
pthread_mutex_unlock(m);
return ret;
}
Run Code Online (Sandbox Code Playgroud)
我担心的是缓存.编译器是否可以在堆栈或寄存器中放置_protected的副本,并在赋值中使用该陈旧值?如果没有,是什么阻止了这种情况发生?这种模式的变化是否易受攻击?
我假设编译器实际上并不理解pthread_mutex_lock()是一个特殊函数,所以我们只是受序列点保护吗?
非常感谢.
更新:好的,我可以看到一个趋势,答案解释了为什么不稳定是坏的.我尊重这些答案,但有关该主题的文章很容易在网上找到.我在网上找不到的,以及我问这个问题的原因,就是我如何保护我没有不稳定.如果上面的代码是正确的,那么缓存问题如何无懈可击?
让我们假设我有一些工作线程如下:
while (1) {
do_something();
if (flag_isset())
do_something_else();
}
Run Code Online (Sandbox Code Playgroud)
我们有几个帮助函数来检查和设置标志:
void flag_set() { global_flag = 1; }
void flag_clear() { global_flag = 0; }
int flag_isset() { return global_flag; }
Run Code Online (Sandbox Code Playgroud)
因此,线程继续do_something()在忙循环中调用,并且如果某些其他线程设置global_flag线程也调用do_something_else()(例如,可以通过从另一个线程设置标志来请求时输出进度或调试信息).
我的问题是:我是否需要做一些特殊的事情来同步对global_flag的访问?如果是,那么以便携方式进行同步的最小工作究竟是什么?
我试图通过阅读许多文章来解决这个问题,但我仍然不太确定正确的答案......我认为它是以下之一:
我们只需要定义标志,volatile以确保每次检查时都从共享内存中读取它:
volatile int global_flag;
Run Code Online (Sandbox Code Playgroud)
它可能不会立即传播到其他CPU核心,但迟早会保证.
在一个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 …Run Code Online (Sandbox Code Playgroud) 这样做的pthread_mutex_lock和调用pthread_mutex_unlock函数调用的存储栅栏/屏障指令?或者像compare_and_swap暗示那样的低级指令是否有内存障碍?
假设我们有以下代码:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void guarantee(bool cond, const char *msg) {
if (!cond) {
fprintf(stderr, "%s", msg);
exit(1);
}
}
bool do_shutdown = false; // Not volatile!
pthread_cond_t shutdown_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t shutdown_cond_mutex = PTHREAD_MUTEX_INITIALIZER;
/* Called in Thread 1. Intended behavior is to block until
trigger_shutdown() is called. */
void wait_for_shutdown_signal() {
int res;
res = pthread_mutex_lock(&shutdown_cond_mutex);
guarantee(res == 0, "Could not lock shutdown cond mutex");
while (!do_shutdown) { // while loop guards against spurious wakeups …Run Code Online (Sandbox Code Playgroud) 如果我有一些看起来像这样的代码:
typedef struct {
bool some_flag;
pthread_cond_t c;
pthread_mutex_t m;
} foo_t;
// I assume the mutex has already been locked, and will be unlocked
// some time after this function returns. For clarity. Definitely not
// out of laziness ;)
void check_flag(foo_t* f) {
while(f->flag)
pthread_cond_wait(&f->c, &f->m);
}
Run Code Online (Sandbox Code Playgroud)
C标准中是否有任何内容阻止优化器将check_flag重写为:
void check_flag(foo_t* f) {
bool cache = f->flag;
while(cache)
pthread_cond_wait(&f->c, &f->m);
}
Run Code Online (Sandbox Code Playgroud)
换句话说,每次循环时生成的代码是否必须跟随f指针,或者编译器是否可以自由地取消引用?
如果它可以自由拉出来,有什么办法可以防止这种情况发生吗?我需要在某处撒一个volatile关键字吗?它不能是check_flag参数,因为我计划在这个结构中有其他变量,我不介意编译器这样优化.
可能我不得不诉诸:
void check_flag(foo_t* f) {
volatile bool* cache …Run Code Online (Sandbox Code Playgroud) 全局指针在线程之间是否存在范围?
例如,假设我有两个文件,file1.c和file2.c:
file1.c中:
uint64_t *g_ptr = NULL;
modify_ptr(&g_ptr) {
//code to modify g_ptr to point to a valid address
}
read_from_addr() {
//code which uses g_ptr to read values from the memory it's pointing to
}
Run Code Online (Sandbox Code Playgroud)
file2.c中:
function2A() {
read_from_addr();
}
Run Code Online (Sandbox Code Playgroud)
所以我有threadA,它运行在file1.c中并执行modify_ptr(&g_ptr)和read_from_addr().然后threadB运行,它通过执行function2A()的file2.c运行.
我的问题是:threadB是否看到g_ptr被修改了?或者它仍然看到它指向NULL?
如果不是这样,那么指针是全局的意味着什么?如何确保在不同线程之间可以访问此指针?
如果我需要澄清任何事情,请告诉我.谢谢
如果我在里面创建一个单例+[NSObject initialize],我是否需要将我的代码放在一个dispatch_once块中?
static NSObject * Bar;
@implementation Foo
+ (void)initialize {
if (self == [Foo class]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Bar = [NSObject new];
});
}
}
@end
Run Code Online (Sandbox Code Playgroud)
编辑
我很关心这个,因为我想确保所有线程都会看到我调用Bar之后设置的+[Foo initialize].文档说+[NSObject initialize]是线程安全的,但这是否意味着它是内存安全的?
concurrency objective-c objective-c-runtime grand-central-dispatch ios
我正在尝试减少代码需要执行的锁定量,并且遇到了一些关于pthread_mutex_lock如何处理其内存障碍的学术问题.为了使这个易于理解,假设互斥锁正在保护一个初始化后完全静态的数据字段,但我想将此设置推迟到第一次访问时.我想写的代码如下:
/* assume the code safely sets data to null at setup,
* and the mutex is correctly setup
*/
if (NULL == data) {
pthread_mutex_lock(&lock);
/* Need to re-check data in case it was already setup */
if (NULL == data)
data = deferred_setup_fcn();
pthread_mutex_unlock(&lock);
}
Run Code Online (Sandbox Code Playgroud)
我看到的可能问题是数据是在锁内设置的,但是在锁外读取.编译器是否可以跨互斥锁定调用缓存数据值?或者我是否必须插入适当的volatile关键字以防止这种情况发生?
我知道用pthread_once调用可以做到这一点,但我想避免使用另一个数据字段(锁已经存在保护相关字段).
指向POSIX线程函数调用内存排序的权威指南的指针也可以很好地工作.