我目前遇到一些关于C ++并发编程的设计问题 我想知道你是否可以帮助我:
假设某些函数func
对某个对象obj
起作用。在这些操作期间,有必要持有一个锁(可能是obj
的成员变量)。现在假设
func
在持有锁时调用子功能func_2
。现在func_2
对已经锁定的对象进行操作。但是,如果我还想在没有按住锁的情况下从其他地方拨打func_2
该怎么办?应该func_2
锁定obj
还是不应该?我看到3个可能性:
bool
传递给func_2
,表明是否需要锁定。
这似乎引入了很多样板代码。obj
中锁定func_2
。递归锁
好像
但是有问题,请参阅here。func_2
的每个来电者都已拥有锁。我会
记录这个并且可能强制执行此操作(至少在调试模式下)。是
让函数做出关于哪些锁是否锁定的假设是合理的
由调用线程持有?更一般地说,我如何从设计角度做出决定
一个函数是否应该锁定Obj
哪个应该假定它已被锁定?
(显然,如果函数假定某些锁被保持,那么它只能调用
至少具有同样强烈假设但除此之外的函数?)我的问题如下:在实践中使用了哪一种方法?为什么?
提前致谢
hfhc2
答案 0 :(得分:1)
<强> 1。传递指示符是否锁定:
您为锁定者提供锁定选择。这很容易出错:
<强> 2。递归锁定:
您已经突出显示了该问题。
第3。将锁定责任传递给来电者:
在您提出的不同替代方案中,这似乎是最一致的。与1相反,你不做出选择,但你完全承担了锁定的责任。这是使用func_2的合同的一部分。
你甚至可以断言是否在对象上设置了一个锁,以防止出现错误(尽管由于你不一定能够确定拥有锁的人是否有限,因此检查会受到限制)。
4.重新考虑您的设计:
如果您需要在func_2中确保对象已被锁定,则表示您必须保护其中的关键部分。两个函数都有可能需要锁定,因为它们对obj执行一些较低级别的操作,并且需要防止对象的不稳定状态下的数据争用。
我强烈建议看看是否可以从func和func_2中提取这些较低级别的例程,并将它们封装在obj上的更简单的原始函数中。这种方法也可以有助于锁定更短的序列,从而增加实际并发的机会。
答案 1 :(得分:1)
好的,就像另一个后续行动一样。我最近阅读了the API documentation of glib,特别是有关消息传递队列的部分。我发现在这些队列上运行的大多数函数有两种变体,名为function
和function_unlocked
。这个想法是,如果程序员想要执行单个操作,比如从队列中弹出,可以使用g_async_queue_pop()
来完成。该功能自动处理队列的锁定/解锁。但是,如果程序员想要不间断地弹出两个元素,可以使用以下序列:
GAsyncQueue *queue = g_async_queue_new();
// ...
g_async_queue_lock(queue);
g_async_queue_pop_unlocked(queue);
g_async_queue_pop_unlocked(queue);
g_async_queue_unlock(queue);
这类似于我的第三种方法。同样也存在关于某些锁定状态的假设,它们是API所要求的并且需要记录。