我有一个共享向量,可以被两个线程访问。
线程A的函数推入向量,线程B的函数完全交换向量进行处理。
MovetoVec(PInfo* pInfo)
{
while(1)
{
if(GetSwitch())
{
swapBucket->push_back(pInfo);
toggles = true;
break;
}
else if(pInfo->tryMove == 5)
{
delete pInfo;
break;
}
pInfo->tryMove++;
Sleep(25);
}
}
线程A尝试将原子布尔值toggles
设置为true并推送到向量中。(上面的MoveToVec
函数将被许多线程调用)。函数GetSwitch定义为
GetSwitch()
{
if(toggles)
{
toggles = false;
return TRUE;
}
else
return FALSE;
}
toggles
这里是atomic_bool.Bnd交换向量的另一个函数是
GetClassObj(vector<ProfiledInfo*>* toSwaps)
{
if(GetSwitch())
{
toSwaps->swap(*swapBucket);
toggles = true;
}
}
如果GetSwitch返回false,则threadB不执行任何操作。在这里我使用任何锁定。它适用于大多数情况。但是有些时候,swapBucket中的一个pInfo
对象是NULL。我知道这是因为同步性差。
我遵循这种类型的GetSwitch()逻辑只是为了忽略锁定造成的开销。我应该放弃这个并回到互斥或关键部分吗?
答案 0 :(得分:5)
您的GetSwitch
实施错误。多个线程可以同时获取交换机。
仅有两个主题的此类场景示例:
Thread 1 | Thread 2
--------------------------|--------------------------
if (toggles) |
| if (toggles)
toggles = false; |
| toggles = false;
if-test和赋值不是原子操作,因此不能用于自己同步线程。
如果要使用原子布尔值作为同步方法,则需要在一次原子操作中比较和交换值。幸运的是,C ++提供了一个名为std::compare_exchange
的操作,它具有弱而强的风格(弱信可能会失败,但在循环中调用时会更便宜)。
使用此操作,您的GetSwitch
方法将变为:
bool GetSwitch()
{
bool expected = true; // The value we expect 'toggles' to have
bool desired = false; // The value we want 'toggles' to get
// Check if 'toggles' is as expected, and if it is, update it to the desired value
bool result = toggles.compare_exchange_strong(&expected, desired);
// The result of the compare_exchange is true if the value was updated and false if it was not
return result;
}
这将确保比较和更新值以原子方式发生。
请注意,C ++标准不保证原子布尔值无锁。在您的情况下,您也可以使用std::atomic_flag
,保证按标准无锁!仔细阅读这个例子,它与原子变量有点不同。
正如您尝试的那样编写无锁代码非常复杂且容易出错。
我的建议是先用锁写代码并确保它是100%正确的。互斥体实际上速度惊人,因此在大多数情况下性能应该没问题。关于锁定性能的一个很好的解读:http://preshing.com/20111118/locks-arent-slow-lock-contention-is
只有在您对代码进行了分析并确信自己锁定正在影响性能之后,您才应该尝试编写无锁代码。然后再次进行配置,因为无锁代码不一定更快。