读和写都要锁住哪一部分?

Which part should I lock when dealing with both read and write?

我正在使用 C++(11) STL 并遇到以下问题。 这段代码的基本思想是: 我有一个 "trigger" 函数,一个 "add" 函数和一个标志(默认为 false)。如果标志为 false,"add" 函数会将 threadID 推入队列,否则会将 threadID 插入集合。当触发函数被调用时,它将标志设置为 "true" 并将 threadIDs 从队列移动到集合中。 我初始化了 100 个线程并将其中一个线程用于 运行 触发函数(在代码中它是线程 NO.30)。理想情况下,结果应该在队列中有 0 个元素,在集合中有 99 个元素。 然而,有时结果是正确的,有时我错过了集合中的一些数字,有时我得到了 EXC_BAD_ACCESS 错误。 有人能帮忙吗?谢谢。

#include <iostream>
#include <thread>
#include <vector>
#include <unordered_set>
#include <queue>
#include <mutex>
#include <atomic>
using namespace std;


bool flag = false;
queue<int> q;
unordered_set<int> s;
mutex mu;

void trigger()
{
    mu.lock();
    flag = true;
    mu.unlock();
    while( !q.empty() ){
        s.insert(q.front());
        q.pop();
    }
}

void add(int id)
{
    mu.lock();
    if( !flag ) 
        q.push(id);
    else {
        if ( s.find(id) == s.end() ){
            s.insert(id);
        }
    }
    mu.unlock();
}

void missing()
{
    cout << "Missing Numbers: ";
    for (int i = 1; i <= 100; i++) {
        if( s.find(i) == s.end() ) 
            cout << i << " ";
    }
    cout << endl;
}

int main()
{
    vector<thread> threads;

    for (int i = 0; i < 100; i++){
        if ( i == 29 ) threads.push_back(thread(trigger));
        else threads.push_back(thread(add, i+1));
    }
    for (int i = 0; i < 100; i++){
        threads[i].join();
    }
    cout << "Q size: " << q.size() << endl;
    cout << "S size: " << s.size() << endl;
    missing();
}

您有 1 个线程执行 trigger 函数,许多线程执行 add 函数。此外,您需要注意保护一些共享状态,而不是全部。在下面的代码片段中查看我的 comments/questions。

void trigger()
{
    // Only the 'flag' is protected from concurrent acceess
    mu.lock();
    flag = true;
    mu.unlock();

    // Why isn't 'q' or 's' protected by a lock?
    while( !q.empty() ){
        s.insert(q.front());
        q.pop();
    }
}

void add(int id)
{
    // In this function both 'q' and 's' are protected from concurrent access
    mu.lock();
    if( !flag ) 
        q.push(id);
    else {
        if ( s.find(id) == s.end() ){
            s.insert(id);
        }
    }
    mu.unlock();
}

可能的解决方案

一般来说,您应该保护并发访问的任何状态。我还建议使用 lock 类型(例如 lock_guard)而不是直接锁定和解锁互斥体(研究 RAII 以了解为什么鼓励这样做)。

#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#include <unordered_set>
#include <vector>

using namespace std;

bool flag = false;
queue<int> q;
unordered_set<int> s;
mutex mu;

void trigger()
{
    lock_guard<mutex> lock(mu);
    flag = true;

    while (!q.empty())
    {
        s.insert(q.front());
        q.pop();
    }
}

void add(int id)
{
    lock_guard<mutex> lock(mu);

    if (!flag)
    {
        q.push(id);
    }
    else
    {
        if (s.find(id) == s.end())
        {
            s.insert(id);
        }
    }
}

void missing()
{
    cout << "Missing Numbers: ";
    for (int i = 1; i <= 100; ++i)
    {
        if (s.find(i) == s.end())
        {
            cout << i << " ";
        }
    }
    cout << endl;
}

int main()
{
    vector<thread> threads;
    for (int i = 0; i < 100; ++i)
    {
        if (i == 29)
        {
            threads.push_back(thread(trigger));
        }
        else
        {
            threads.push_back(thread(add, i + 1));
        }
    }

    for (int i = 0; i < 100; ++i)
    {
        threads[i].join();
    }

    cout << "Q size: " << q.size() << endl;
    cout << "S size: " << s.size() << endl;
    missing();

    return 0;
}