Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/161.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么基于锁的程序不能组成正确的线程安全片段?_C++_Multithreading_C++11_Concurrency_Mutex - Fatal编程技术网

C++ 为什么基于锁的程序不能组成正确的线程安全片段?

C++ 为什么基于锁的程序不能组成正确的线程安全片段?,c++,multithreading,c++11,concurrency,mutex,C++,Multithreading,C++11,Concurrency,Mutex,蒂姆·哈里斯说: 也许最根本的反对[…]是基于锁的 程序不组合:组合时,正确的片段可能会失败。对于 例如,考虑具有线程安全插入和删除的哈希表。 操作。现在假设我们要从表中删除一项 t1,并将其插入表t2中;但是中间状态(其中 这两个表都不包含该项)对其他线程不可见。 除非哈希表的实现者预见到这种需要,否则 根本无法满足这一要求。[……]简言之, 无法删除单独正确的操作(插入、删除) 组合成更大的正确操作-Tim Harris等人。, “可组合内存事务”,第2节:背景,第2[6]页 这是什么意思

蒂姆·哈里斯说:

也许最根本的反对[…]是基于锁的 程序不组合:组合时,正确的片段可能会失败。对于 例如,考虑具有线程安全插入和删除的哈希表。 操作。现在假设我们要从表中删除一项 t1,并将其插入表t2中;但是中间状态(其中 这两个表都不包含该项)对其他线程不可见。 除非哈希表的实现者预见到这种需要,否则 根本无法满足这一要求。[……]简言之, 无法删除单独正确的操作(插入、删除) 组合成更大的正确操作-Tim Harris等人。, “可组合内存事务”,第2节:背景,第2[6]页

这是什么意思

如果我有两个哈希映射
std::unordered_map
和两个互斥体
std::mutex
(每个哈希映射一个),那么我可以简单地锁定这两个:

并将在以下情况下使用:

my_unordered_map<std::string, std::string> map1 ( {{"apple","red"},{"lemon","yellow"}} );

my_unordered_map<std::string, std::string> map2 ( {{"orange","orange"},{"strawberry","red"}} );

void func() {
    std::lock_guard<my_unordered_map> lock1(map1);
    std::lock_guard<my_unordered_map> lock2(map2);

    // work with map1 and map2
    // recursive_mutex allow multiple locks in: lock1(map1) and map1->at(key)
}
myu无序地图map1({{“苹果”、“红色”}、{“柠檬”、“黄色”});
my_无序地图地图2({{“橙色”、“橙色”}、{“草莓”、“红色”});
void func(){
std::锁和防护锁1(map1);
std::锁和防护锁2(map2);
//使用map1和map2
//递归\u互斥允许多个锁入:lock1(map1)和map1->at(key)
}
类似地,我得到了map1和map2的线程安全代码和完全顺序一致性

但你知道这是关于哪些案例的吗

也许最根本的反对[…]是基于锁的 程序不组合:组合时,正确的片段可能会失败


您的程序本身非常好

对于另一个线程中的不同任务,另一个程序可能会使用

void func_other() {
    std::lock_guard<my_unordered_map> lock2(map2);
    std::lock_guard<my_unordered_map> lock1(map1);

    // work with map1 and map2
}
void func_other(){
std::锁和防护锁2(map2);
std::锁和防护锁1(map1);
//使用map1和map2
}
再说一次,这本身是好的

但是,如果我们同时运行两个程序,可能会出现死锁。线程1锁映射1,线程2锁映射2,现在两个线程中的下一个锁将永远等待

因此,我们不能天真地编写这两个程序


使用STM而不是锁将始终允许此类组合(以一定的性能价格)。

插入和擦除的线程安全原子操作通常隐藏其中的互斥。这样可以防止在不锁定的情况下访问

在本例中,您将公开互斥锁。这需要每个用户正确地处理互斥锁,否则互斥锁就会中断

在完全访问互斥体的情况下,您可以排除安全地看到中间状态的可能性:您的代码失败,因为它没有使用
std::lock
来保证互斥体锁定顺序是全局一致的,如果其他代码使用不同的锁定顺序,这可能会导致死锁


这类问题——您必须不断了解事务需要哪些互斥体,以及您持有哪些互斥体——并不会分解为小的、易于确定的正确部分。正确性变得非本地,然后复杂性爆发,bug大量出现。

大概是为了让操作在事务上失败:只有在map2还不包含密钥k的情况下,才从map1中提取密钥k。因为事务需要将两个互斥锁锁定在一起,所以每个映射只能使用一个私有互斥锁来实现这一点。谢谢。是的,如果更改锁的顺序,基于锁的容器可能会有死锁,即“组合时可能会失败”意味着我们可以重写此组合代码以避免任何失败。但两个无锁容器一般不能保证两个表的一致性。也就是说,只有STM可以保证两个表的一致性不存在故障。但是,与任何事务系统一样,STM可能会在事务回滚时生成事务提交失败(内部死锁或更新冲突),我们可以在软件中处理,不是吗?@Alex Yes。在大多数STM方法中,即使存在一些冲突,也会提交一个事务,而其他事务可能会回滚。然后,STM引擎可以重试回滚事务。我有两种方法:1。我应该将STM与受控事务循环一起使用,直到它们完成执行。2.或者我应该自己解决基于锁的程序中的死锁,例如,通过使用
互斥体。对于许多必需的互斥体中的每一个,在循环中尝试锁定()
,也可以在每一定次数的重复之后使用
std::this\u-thread::yield()
,如果
互斥体。在一定时间(1usec或1msec,…)之后尝试锁定()==false
然后抛出异常
抛出我的回滚事务(),然后捕获它,回滚所有所做的更改并再次重试循环以一起更改一些表
作为私人成员,只能由
std::lock\u guard
作为朋友直接使用。我用rai
std::lock\u guard
以任何方法锁定这个互斥锁,例如:
insert()
erase()
find()
。另外,我建议在一个锁中使用
std::lock\u guard
手动锁定容器,以执行多个操作:查找擦除插入,两个表一致性。。。我认为基于锁的程序只有一个潜在的问题:不同锁顺序上的死锁。
my_unordered_map<std::string, std::string> map1 ( {{"apple","red"},{"lemon","yellow"}} );

my_unordered_map<std::string, std::string> map2 ( {{"orange","orange"},{"strawberry","red"}} );

void func() {
    std::lock_guard<my_unordered_map> lock1(map1);
    std::lock_guard<my_unordered_map> lock2(map2);

    // work with map1 and map2
    // recursive_mutex allow multiple locks in: lock1(map1) and map1->at(key)
}
void func_other() {
    std::lock_guard<my_unordered_map> lock2(map2);
    std::lock_guard<my_unordered_map> lock1(map1);

    // work with map1 and map2
}