C++ 从单个工作线程更新全局变量:我需要互斥锁吗?

C++ 从单个工作线程更新全局变量:我需要互斥锁吗?,c++,multithreading,opencv,mutex,C++,Multithreading,Opencv,Mutex,这似乎是一个问题,但我不会得出任何明确的结论。我需要一点帮助来决定我是应该还是必须!在访问/修改全局变量时,在以下情况下执行锁定代码: 在文件范围内定义的全局变量 单个工作线程读取/写入全局变量 来自主进程线程的调用,调用返回这些全局变量的访问器函数 所以问题是,我应该用互斥锁锁定对全局变量的访问吗 更具体地说,我正在编写一个C++库,它使用一个摄像头来跟踪一个纸页上的对象——计算机视觉是CPU密集型的,所以性能是至关重要的。我有一个工作线程,它是在一个开放函数中分离出来的。此线程处理所有对象跟

这似乎是一个问题,但我不会得出任何明确的结论。我需要一点帮助来决定我是应该还是必须!在访问/修改全局变量时,在以下情况下执行锁定代码:

在文件范围内定义的全局变量 单个工作线程读取/写入全局变量 来自主进程线程的调用,调用返回这些全局变量的访问器函数 所以问题是,我应该用互斥锁锁定对全局变量的访问吗

更具体地说,我正在编写一个C++库,它使用一个摄像头来跟踪一个纸页上的对象——计算机视觉是CPU密集型的,所以性能是至关重要的。我有一个工作线程,它是在一个开放函数中分离出来的。此线程处理所有对象跟踪。当调用Close函数时,它通过全局标志间接终止

感觉上我只是在要求内存损坏,但我没有发现死锁问题,也没有遇到从这些访问器函数返回的任何错误值。经过几个小时的研究,我得到的总体印象是,嗯,可能吧。无论什么祝你玩得开心。如果我真的应该使用互斥体,为什么我还没有遇到任何问题呢

以下是对我当前计划的过度简化:

// *********** lib.h ***********
// Structure definitions
struct Pointer
{
  int x, y;
};
// more...

// API functions
Pointer GetPointer();
void Start();
void Stop();
// more...
实现如下所示

// *********** lib.cpp ***********
// Globals
Pointer p1;
bool isRunning = false;
HANDLE hWorkerThread;
// more...

// API functions
Pointer GetPointer()
{
  // NOTE: my current implementation is actually returning a pointer to the
  // global object in memory, not a copy of it, like below...

  // Return copy of pointer data
  return p1;
}

// more "getters"...

void Open()
{
  // Create worker thread -- continues until Close() is called by API user
  hWorkerThread = CreateThread(NULL, 0, DoWork, NULL, 0, NULL);
}

void Close()
{
  isRunning = false;

  // Wait for the thread to close nicely or else you WILL get nasty
  // deadlock issues on close
  WaitForSingleObject(hWorkerThread, INFINITE);
}

DWORD WINAPI DoWork(LPVOID lpParam)
{
  while (isRunning)
  {
    // do work, including updating 'p1' about 10 times per sec
  }

  return 0;
}
最后,从外部可执行文件调用此代码。类似这样的伪代码:

// *********** main.cpp ***********
int main()
{
  Open();

  while ( <esc not pressed> )
  {
    Pointer p = GetPointer();
    <wait 50ms or so>
  }
  Close();
}
我是否应该采取不同的方法?这个非问题今天让我抓狂:-/我需要确保这个库是稳定的,并且返回准确的值。如有任何见解,将不胜感激


谢谢

如果只有一个线程同时读取和写入对象,则不需要锁

如果对象是只读的,则不需要锁。假设您可以保证在构造期间只有一个线程访问对象


如果任何线程写入,将更改对象的状态。如果有其他线程访问该对象,则必须锁定所有读写访问。尽管您可以使用允许多个读卡器的读锁。但是写入操作必须是独占的,并且在状态更改时,任何读卡器都不能访问对象。

不会出现死锁,但您可能会偶尔看到一些极低概率的坏值:因为读取和写入只需几分之一纳秒,而您每秒只读取变量50次,碰撞的可能性大约是5000万分之一

如果在Intel 64上发生这种情况,指针将与8字节边界对齐,并在一次操作中读取和写入所有8字节,使用一条汇编指令,则访问是原子的,不需要互斥

如果这两个条件中的任何一个都不满足,那么读卡器就有可能得到错误的数据


为了安全起见,我会设置一个互斥锁,因为它每秒只会使用50次,不会造成性能问题。

我想这取决于您在DoWork函数中所做的操作。假设它将一个点值写入p1。至少您存在以下可能的竞态条件,该竞态条件将向主线程返回无效结果:

假设工作线程想要更新p1的值。例如,让我们将p1的值从A、B更改为C、D。这将至少涉及两个操作,将C存储在x中,将D存储在y中。如果主线程决定在GetPointer函数中读取p1的值,那么它还必须执行至少两个操作,x的load value和y的load value。如果操作顺序为:

更新线程:storec 主线程:加载x主线程接收C 主线程:加载y主线程接收B 更新线程:存储D 主线程将获得不正确的点C、B


这个特殊的问题不是线程的好用途,因为主线程没有做任何实际的工作。我将使用一个线程和一个API,比如WaitForMultipleObjectsEx,它允许您同时等待来自键盘stdin句柄的输入、来自相机的I/O事件和超时值。

由于指针中信息的性质,您可能看不到问题。如果它正在跟踪某个移动速度不是很快的对象的坐标,并且在读取过程中更新了位置,则坐标可能有点偏离,但不足以引起注意


例如,假设更新后,p.x为100,p.y为100。您正在跟踪的对象移动了一点,因此在下一次更新后,p.x为102,p.y为102。如果你恰好在这个更新的中间阅读,在X被更新,但是在Y被更新之前,你将得到一个指针值为Px为102,Py为100的指针值。

< P>这种情况是相当清楚的-在某些触发同步的过程中,读者可能看不到更新。 互斥、内存屏障、原子操作。。。。进程所做的许多事情都会隐式触发这种同步-例如,Usenet线程常见问题解答中解释的出于原因的外部函数调用http://www.lambdacs.com/cpt/FAQ.html -请参阅Dave Butenhof的答案,我们需要volatile,因此,如果您的代码所处理的值足够小,不能写一半,例如数字而不是字符串,固定地址而不是动态重新分配,那么它可以在没有显式同步的情况下缓慢运行


如果您对性能的想法是通过编写代码获得更多的循环,那么如果不考虑同步,您将得到一个更好的数字。但是如果你感兴趣的是最小化平均和最坏情况下的延迟,以及读者CAM实际上看到了多少不同的更新,那么你应该和作者同步。

也许一些基金会会有帮助:谢谢大家的精彩回答,特别是向我暗示读者-作者互斥的想法,似乎这些概念在大学里没有得到太多的报道。。。。我最终使用了boost::shared_互斥锁来锁定对主程序循环中修改的每个全局变量的访问。看见如果有人感兴趣,我可以提供一个我最终解决方案的简单例子。你的一些假设是有问题的。他的代码似乎不太可能以原子方式更新x和y成员,尽管我认为如果CPU支持64位原子存储操作,编译器可以以这种方式对其进行优化。另一个问题是,如果没有内存屏障,就不能保证主线程必须读取主线程中的内存,也不能保证更新线程实际上必须写入内存。如果他可以利用CPU/OS提供的原子操作,那么他就不需要互斥体了。+1…当然,除非你可以在没有锁的情况下原子地更新状态-谢谢你解释这个概念。虽然很难验证这是否发生在我的程序中,但我能够在测试程序中使用线程休眠命令来指示长操作,从而创建此无效条件。如果我的x和y值在一两次迭代中像这样支离破碎,对最终用户来说可能无关紧要,但我不能让它过去最终,我使用了Boost库的shared_互斥锁和writer优先锁……我所要做的就是重组整个程序,啊!谢谢你的意见。你的评论把我逼到了极点,说服我在变量上实现多读单写器锁。