在 C++ 中使用槽向量和互斥量的线程的互斥槽分配器

Mutually exclusive slot allocator for threads using a vector of slots and mutexes in C++

如果这篇文章已经发布到其他地方,我很抱歉,但我找不到任何匹配的内容。

在class中我们找到了一个预先设计的槽分配器,它存储了指定数量的槽,这些槽可以被线程获取。如果需要一个插槽,则该插槽应该工作,否则它将被阻塞,直到另一个线程释放。代码如下所示:

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 assert(slots[slot] == true); 我很快发现,有些东西不起作用。经过研究和多种测试方法后,我必须承认我没有想法...

我不明白为什么这不起作用。我最初的想法是 mutexes[i].lock(); 可能是错误,但由于这只是一个读取操作,所以这不是真正的答案。

谢谢。

只使用一个互斥量和一个 condition variable 怎么样?请参阅 cppreference 中的示例并考虑重新设计您的解决方案。

std::vector<bool> 作为动态位集有效地实现。标准没有指定具体的实现方式,但将实现为某种整数类型(例如 std::uint32_t)的连续序列,其中每个位代表 vector 中的一个 bool 值].

因此,将任何一个值设置为 true false 实际上是在修改更大对象的一部分:内部整数对象。您不能简单地锁定正在修改的单个位并期望 thread-safety,因为多个线程可能同时改变该整数

正确同步的唯一方法是不使用 vector<bool> 而是使用类似 std::vector<std::uint8_t> 的东西,其中每个 uint8_t 都是一个可以独立锁定的唯一对象并修改。


举一个具体的例子,假设 vector<bool> 中使用的底层 int 是 uint32_t —— 这样您一次可以在 32 位上进行操作。

现在假设设置一个位是由这三个操作执行的:

  1. 将整数值加载到寄存器
  2. 执行或操作将位设置为1,并且
  3. 将整数值存回内存

如果整数从值 0x0 开始(所有位都设置为零),并且我们有两个线程试图同时设置位 01,则不会 被互斥锁锁定,因为每个位都有一个单独的互斥锁。相反,我们可能会看到以下竞争条件:

 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。从技术上讲,对于未定义的行为,一切皆有可能。