在 C++ 中使用插槽和互斥体向量的线程的互斥插槽分配器
Posted
技术标签:
【中文标题】在 C++ 中使用插槽和互斥体向量的线程的互斥插槽分配器【英文标题】:Mutually exclusive slot allocator for threads using a vector of slots and mutexes in C++ 【发布时间】:2020-11-09 18:46:46 【问题描述】:如果这已发布在其他任何地方,我非常抱歉,但我找不到任何匹配的内容。
在一个类中,我们发现了一个预先设计的槽分配器,它存储了线程可以获取的指定数量的槽。如果需要一个槽,则该槽应该工作,否则它会被阻塞,直到另一个线程释放。代码如下所示:
struct slot_allocator_mutexes
private:
int num_slots = 8;
vector<bool> slots;
vector<mutex> mutexes;
public:
slot_allocator_mutexes() : slots(num_slots, false), mutexes(num_slots)
int acquire_slot()
while(true)
for (int i = 0; i < num_slots; ++i)
mutexes[i].lock();
vector<bool>::reference slot_ref = slots[i];
if (slot_ref == false)
slot_ref = true;
mutexes[i].unlock();
return i;
mutexes[i].unlock();
void release_slot(int slot)
mutexes[slot].lock();
assert(slots[slot] == true);
slots[slot] = false;
mutexes[slot].unlock();
;
将通过
调用 for (int t = 0; t < thread_numbers; ++t)
threads.push_back(thread([&]()
for (int r = 0; r < repeats; ++r)
int slot = alloc.acquire_slot();
cout_lock.lock();
cout << "iteration num: " << r << endl;
cout << "Current slot: " << slot << " in thread number " << pthread_self() << endl;
cout_lock.unlock();
alloc.release_slot(slot);
));
澄清一下:我知道可能会有更简洁的实现 - 不过这不是我的问题。
我的任务是确定这是否是分配器的正确线程安全实现。使用断言assert(slots[slot] == true);
我很快就发现,有些东西不起作用。在进行了研究和多种测试方法之后,我必须承认我没有想法......
我不明白为什么这不起作用。我最初的想法是mutexes[i].lock();
可能是问题所在,但由于这只是一个读取操作,所以这并不是真正的答案。
谢谢。
【问题讨论】:
您似乎没有向我们提供所有代码。m
是什么?我看到m.unlock()
。此外,您似乎在构造函数中命名为 mutexes
locks
。请确保您的最小可重现示例没有这些差异,否则它实际上只是一个猜谜游戏
@Human-Compiler 对这些错误非常抱歉,我更改了一些代码并且从那时起没有编译。现在应该可以工作了
您还没有发布minimal reproducible example。无论如何,答案实际上是一个非常奇怪的 c++ 怪癖,我相信一些 c++ 标准的作者现在非常后悔。如果您尝试用auto
替换难看的显式类型名称并想知道为什么编译器拒绝该代码,您会发现问题。
@EOF 我想你说的是vector<bool>::reference slot_ref = slots[i];
?如果我将其更改为自动它仍然为我编译...?
但是如果你把它改成auto slot_ref = slots[i];
,你就改变了意思,因为slot_ref
不是一个引用而是一个副本。
【参考方案1】:
std::vector<bool>
被有效地实现为动态位集。执行此操作的确切方式未在标准中指定,但将实现为某个整数类型的连续序列(例如std::uint32_t
),其中每个位代表vector
中的bool
值。
因此,将任何一个值设置为true
或 false
实际上是在修改更大对象的一部分:内部整数对象。您不能简单地锁定正在修改的单个位并期望线程安全,因为可能有多个线程同时改变该整数。
正确同步的唯一方法是不使用vector<bool>
,而是使用std::vector<std::uint8_t>
之类的东西,其中每个uint8_t
都是可以独立锁定和修改的唯一对象。
作为一个具体的例子,假设vector<bool>
中使用的底层 int 是 uint32_t
-- 这样您就可以一次在 32 位上进行操作。
现在想象一下,通过这三个操作来设置一个位:
-
将整数值加载到寄存器中
执行 OR 操作将位设置为
1
,并且
将整数值存储回内存
如果整数从值0x0
(所有位设置为零)开始,并且我们有两个线程试图同时设置位0
和1
,那么不会 em> 是互斥锁的任何锁定,因为每个位都有一个单独的互斥锁。相反,我们可能会看到以下竞争条件:
Thread 1 Thread 2
Lock mutex for bit 0
Lock mutex for bit 1
Load <int> into register
Load same <int> into register
OR 0x1 with the register
OR 0x2 with the register
Write register back to mem
Write register back to mem
Unlock mutex for bit 0
unlock mutex for bit 1
请注意,在这种情况下,线程 2 将破坏线程 1 的值。形式上,这实际上只是未定义的行为,我们在这一点上看到的任何东西都完全没有被标准定义。我们可以看到0x1
,我们可以看到0x2
,或者如果事情的顺序正确,我们可以看到0x3
。从技术上讲,如果行为未定义,一切皆有可能。
【讨论】:
哇,完美的答案!非常感谢...老实说,这是一个非常愚蠢的优化。作为用户,我希望向量的每个元素都是单独的类型...vector<bool>
专业化绝对是标准库中最具争议和令人遗憾的设计决策之一。不幸的是,它不太可能真正改变太多,以避免破坏向后兼容性......顺便说一句,如果这回答了您的问题,请确保将其标记为解决方案! :)【参考方案2】:
只使用一个互斥锁和一个condition variable 怎么样?请查看 cppreference 中的示例并考虑重新设计您的解决方案。
【讨论】:
如前所述,我的目标不是实现另一个正确的实现,而是理解为什么这不是线程安全的以上是关于在 C++ 中使用插槽和互斥体向量的线程的互斥插槽分配器的主要内容,如果未能解决你的问题,请参考以下文章