Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/67.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在线程之间共享变量是否存在编译器优化问题?_C++_C_Multithreading_Pthreads - Fatal编程技术网

C++ 在线程之间共享变量是否存在编译器优化问题?

C++ 在线程之间共享变量是否存在编译器优化问题?,c++,c,multithreading,pthreads,C++,C,Multithreading,Pthreads,考虑下面的例子。目标是使用两个线程,一个用来“计算”一个值,另一个用来消耗和使用计算出的值(我试图简化这个)。计算线程向另一个线程发出信号,表示该值已计算完毕,并已通过使用条件变量准备就绪,之后等待的线程将使用该值 // Hopefully this is free from errors, if not, please point them out so I can fix // them and we can focus on the main question #include <p

考虑下面的例子。目标是使用两个线程,一个用来“计算”一个值,另一个用来消耗和使用计算出的值(我试图简化这个)。计算线程向另一个线程发出信号,表示该值已计算完毕,并已通过使用条件变量准备就绪,之后等待的线程将使用该值

// Hopefully this is free from errors, if not, please point them out so I can fix
// them and we can focus on the main question
#include <pthread.h>
#include <stdio.h>

// The data passed to each thread. These could just be global variables.
typedef struct ThreadData
{
  pthread_mutex_t mutex;
  pthread_cond_t cond;
  int spaceHit;
} ThreadData;

// The "computing" thread... just asks you to press space and checks if you did or not
void* getValue(void* td)
{
  ThreadData* data = td;

  pthread_mutex_lock(&data->mutex);

  printf("Please hit space and press enter\n");
  data->spaceHit = getchar() == ' ';
  pthread_cond_signal(&data->cond);

  pthread_mutex_unlock(&data->mutex);

  return NULL;
}

// The "consuming" thread... waits for the value to be set and then uses it
void* watchValue(void* td)
{
  ThreadData* data = td;

  pthread_mutex_lock(&data->mutex);
  if (!data->spaceHit)
      pthread_cond_wait(&data->cond, &data->mutex);
  pthread_mutex_unlock(&data->mutex);

  if (data->spaceHit)
      printf("You hit space!\n");
  else
    printf("You did NOT hit space!\n");

  return NULL;
}

int main()
{
  // Boring main function. Just initializes things and starts the two threads.
  pthread_t threads[2];
  pthread_attr_t attr;
  ThreadData data;
  data.spaceHit = 0;

  pthread_mutex_init(&data.mutex, NULL);
  pthread_cond_init(&data.cond, NULL);

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
  pthread_create(&threads[0], &attr, watchValue, &data);
  pthread_create(&threads[1], &attr, getValue, &data);

  pthread_join(threads[0], NULL);
  pthread_join(threads[1], NULL);

  pthread_attr_destroy(&attr);
  pthread_mutex_destroy(&data.mutex);
  pthread_cond_destroy(&data.cond);

  return 0;
}
我有点偏执,编译器的静态分析可能会确定
data->spaceHit
在两个
if
语句之间没有变化,从而证明使用
data->spaceHit
的旧值而不是重新获取新值是合理的。我对线程和编译器优化的了解还不够,不知道这段代码是否安全。是吗


注:我在C中写了这个,并把它标记为C和C++。我在C++库中使用这个,但是因为我使用C线程API(pTrand和Win32线程),并且可以在C++库的这个部分嵌入C,所以我把它标记为C和C++。(稍后编辑:后面的答案似乎为这个问题提供了更好的答案。我将把这个答案留在这里,作为一种方法的参考,这种方法也不能回答这个问题。建议您推荐一种不同的方法。)

类型
ThreadData
本身不是易失性的

main()
中将其实例化为“data”是易失性的。
getValue()
和watchValueValue()中的指针“data”也指向“ThreadData”类型的易失性版本

虽然我喜欢第一个答案,因为它很紧凑,重写

ThreadData data;  // main()
ThreadData* data; // getValue(), watchValueValue()


可能更好。它将确保对ThreadData成员的任何访问始终被重新读取且未进行优化。如果您向
ThreadData
添加其他字段,也会受到同样的保护。

一般来说,在线程之间共享数据时,不仅存在编译器优化问题,而且还存在硬件优化问题ose线程位于不同的处理器上,可以无序执行指令

但是,
pthread\u mutex\u lock
pthread\u mutex\u unlock
函数不仅必须击败编译器缓存优化,还必须击败任何硬件重新排序优化通过执行解锁,这必须与其他线程一致。例如,在另一个处理器上不能显示锁已释放,但共享变量的更新尚未完成。因此,函数必须执行任何必要的内存障碍。如果编译器可以移动数据访问,所有这些都将无效对函数调用进行循环,或在寄存器级别缓存内容,从而破坏一致性

因此,从这个角度来看,您拥有的代码是安全的。但是,它还有其他问题。
pthread\u cond\u wait
函数应该始终在重新测试变量的循环中调用,因为可能会出于任何原因被虚假唤醒

条件的信号是无状态的,因此等待的线程可能会永远阻塞。仅仅因为在
getValue
输入线程中无条件调用
pthread\u cond\u signal
,并不意味着
watchValue
将通过等待。有可能
getValue
首先执行,然后
spaceHit
未设置。然后
watchValue
进入互斥锁,发现
spaceHit
为false,并执行一个可能不确定的等待。(讽刺的是,唯一能保存它的是虚假唤醒,因为没有循环。)

基本上,您要寻找的逻辑是一个简单的信号量:

// Consumer:
wait(data_ready_semaphore);
use(data);

// Producer:
data = produce();
signal(data_ready_semaphore);
在这种类型的交互中,我们不需要互斥,在您的
watchValue
中不受保护地使用
data->spaceHit
就暗示了这一点。更具体地说,POSIX信号量语法:

// "watchValue" consumer
sem_wait(&ready_semaphore);
if (data->spaceHit)
  printf("You hit space!\n");
else
  printf("You did NOT hit space!\n");

// "getValue" producer
data->spaceHit = getchar() == ' ';
sem_post(&ready_semaphore);
也许您简化为示例的实际代码可以只使用信号量

p.S.
pthread\u cond\u signal
也不需要在互斥锁内。它可能会调用操作系统,因此一个互斥锁保护区域只需要几条机器指令就可以保护共享变量,因为它包含信号调用,所以可能会爆炸到数百个机器周期。

不允许piler在调用
pthread\u cond\u wait()
pthread\u mutex\u unlock()
时缓存
data->spaceHit
的值。这两个函数都被专门称为,必须充当编译器的屏障


要使编译器成为一致性pthreads实现的一部分,它不能在您给出的情况下执行优化。

要么我没有正确理解您,要么您弄错了。
main()中的
数据的实例化
不是易变的
。线程函数中的
td
data
指针也没有标记为
volatile
。你能澄清一下吗?我建议使用(主要标记为voltile。这将防止编译器对“数据”使用任何优化,因为它可以随时更改。只有
ThreadData::spaceHit
需要标记为volatile(直接在
struct
定义中)。当然,标记
data
volatile也会使其所有成员都不稳定,但最好只标记
ThreadData::spaceHit
volatile,因为这样你就不会有忘记它的风险(否则,您必须将每个
ThreadData
实例标记为volatile,并确保所有指针都保留限定符,这很容易出错)。@chux:这一点也不错。不过,我认为constness通常是实例或别名的问题(有些需要,有些不需要)而在这种情况下,由于该特定数据成员的多线程使用,要求具有易失性
// Consumer:
wait(data_ready_semaphore);
use(data);

// Producer:
data = produce();
signal(data_ready_semaphore);
// "watchValue" consumer
sem_wait(&ready_semaphore);
if (data->spaceHit)
  printf("You hit space!\n");
else
  printf("You did NOT hit space!\n");

// "getValue" producer
data->spaceHit = getchar() == ' ';
sem_post(&ready_semaphore);