似乎这个问题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();
}
我应该采取不同的方法吗?这个没有问题的问题让我疯狂: - /我需要确保这个库稳定并返回准确的值。任何见解将不胜感激。
由于
答案 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加载值。如果操作顺序是:
主线程将得到点(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。