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

时间:2010-10-20 23:57:08

标签: c++ multithreading opencv mutex

似乎这个问题gets asked frequently,但我没有得出任何明确的结论。我需要一些帮助来确定在我访问/修改全局变量时是否应该(或必须!)实现锁定代码:

  • 在文件范围定义的全局变量
  • 读取/写入全局变量的单个“工作者”线程
  • 从主进程线程调用调用返回这些全局变量的访问器函数

所以问题是,我应该使用互斥锁来锁定对全局变量的访问吗?

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

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

这是对我当前程序的过度简化:

// *********** 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();
}

我应该采取不同的方法吗?这个没有问题的问题让我疯狂: - /我需要确保这个库稳定并返回准确的值。任何见解将不胜感激。

由于

5 个答案:

答案 0 :(得分:5)

如果只有一个线程访问一个对象(读取和写入),则不需要锁定。

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

如果任何线程写入(更改状态)对象。如果有其他线程访问该对象,则必须锁定 ALL 访问(读取和写入)。虽然您可以使用允许多个读取器的读锁定。但是写操作必须是独占的,没有读者可以在状态被更改时访问该对象。

答案 1 :(得分:1)

你不会遇到死锁,但是你可能会看到一些偶然的坏值,而且概率非常低:因为读取和写入需要几分之一秒,而你只能每秒读取变量50次,所以碰撞就像千万分之一。

如果在Intel 64上发生这种情况,“指针”与8字节边界对齐,并且在一次操作中读取和写入(所有8个字节都带有一个汇编指令),然后访问是原子的,你不要需要一个互斥锁。

如果不满足其中任何一个条件,读者可能会收到不良数据。

我只是为了安全起见而设置一个互斥锁,因为它只会被使用50次,而且不会出现性能问题。

答案 2 :(得分:1)

我想这取决于你在DoWork()函数中做了什么。我们假设它将一个点值写入p1。至少你有一个竞争条件的可能性,它会将无效的结果返回给主线程:

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

  1. 更新主题:存储C
  2. 主线程:加载x(主线程接收C)
  3. 主线程:加载y(主线程接收B)
  4. 更新主题:商店D
  5. 主线程将得到点(C,B),这是不正确的。

    这个特殊问题不是好用的线程,因为主线程没有做任何实际的工作。我会使用一个单独的线程,以及一个像WaitForMultipleObjectsEx这样的API,它允许你同时等待来自键盘stdin句柄的输入,来自摄像机的I / O事件和超时值。

答案 3 :(得分:1)

情况非常明确 - 读者可能看不到更新,直到某些东西触发同步(互斥,内存屏障,原子操作......)。许多事情过程会隐式地触发这种同步 - 例如外部函数调用(有理由解释了Usenet线程常见问题解答(http://www.lambdacs.com/cpt/FAQ.html) - 请参阅Dave Butenhof的回答需要volatile,所以如果你的代码处理的是小的值足够他们不能半写(例如数字而不是字符串,固定地址而不是动态(重新)分配)然后它可以在没有显式同步的情况下跛行。

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

答案 4 :(得分:0)

由于Pointer中信息的性质,您可能没有看到问题。如果跟踪某些物体的坐标移动速度不是很快,并且在读取过程中位置会更新,那么坐标可能会“略微偏离”,但不足以引起注意。

例如,假设更新后,px为100,py为100.您跟踪的对象移动了一点,因此在下次更新后,px为102,py为102.如果您正好在读取在此更新的中间,在更新x之后但在更新y之前,您将结束将指针值px设置为102,并将py设置为100。