c++ - 使用 Mutex 挂起程序

标签 c++ multithreading concurrency mutex

我正在尝试学习 C++ 中的并发编程。

我使用 push()、pop()、top() 和 empty() 方法实现了一个基本的堆栈类。

我创建了两个线程,它们都将尝试访问顶部元素并将其弹出,直到堆栈变空。

首先,我尝试在不使用互斥量的情况下实现它,输出是乱码,最后导致 segfault,这是预期的,因为操作不是原子的,所以数据竞争是不可避免的.

所以我尝试用互斥锁来实现它,但由于没有解锁互斥锁,程序甚至没有给出任何输出就挂了。

现在,我已经正确地使用了互斥锁锁定+解锁序列,我的程序根据需要给出了正确的输出,但之后程序挂起——可能是由于线程仍在执行或控制未到达主线?

#include <thread>
#include <mutex>
#include <string>
#include <iostream>
#include <vector>

using std::cin;
using std::cout;
std::mutex mtx;
std::mutex a_mtx;


class MyStack
{
    std::vector<int> stk;
public:
    void push(int val) {
        stk.push_back(val);
    }

    void pop() {
        mtx.lock();
        stk.pop_back();
        mtx.unlock();
    }

    int top() const {
        mtx.lock();
        return stk[stk.size() - 1];
    }

    bool empty() const {
        mtx.lock();
        return stk.size() == 0;
    }
};

void func(MyStack& ms, const std::string s)
{
    while(!ms.empty()) {
        mtx.unlock();
        a_mtx.lock();
        cout << s << " " << ms.top() << "\n";
        a_mtx.unlock();
        mtx.unlock();
        ms.pop();
    }

    //mtx.unlock();
}

int main(int argc, char const *argv[])
{
    MyStack ms;

    ms.push(3);
    ms.push(1);
    ms.push(4);
    ms.push(7);
    ms.push(6);
    ms.push(2);
    ms.push(8);

    std::string s1("from thread 1"), s2("from thread 2");
    std::thread t1(func, std::ref(ms), "from thread 1");
    std::thread t2(func, std::ref(ms), "from thread 2");

    t1.join();
    t2.join();

    cout << "Done\n";

    return 0;
}

我想是因为一旦堆栈为空,我就没有解锁互斥量。因此,当我取消注释注释行并运行它时,它会给出乱码输出和段错误。

我不知道我哪里做错了。这是编写线程安全堆栈类的正确方法吗?

最佳答案

it gives gibberish output and segfault.

在当前的同步方案下,它仍然可能会给您带来段错误,即使您使用建议的 RAII style locking像这样:

void pop() {
    std::lock_guard<std::mutex> lock{ mtx };
    stk.pop_back();
}

int top() const {
    std::lock_guard<std::mutex> lock{ mtx };
    return stk[stk.size() - 1];
}

bool empty() const {
    std::lock_guard<std::mutex> lock{ mtx };
    return stk.size() == 0;
}

因为你没有照顾race-condition 在不同线程对这些方法的两次后续调用之间产生。例如,想想当堆栈只剩下一个元素时会发生什么,一个线程询问它是否为空并得到一个 false 然后你有一个上下文切换而另一个线程得到相同的 false 同样的问题。所以他们都在争夺top()pop()。虽然第一个已经弹出它然后另一个尝试 top() 它会在 stk.size() - 1 产生 的情况下这样做-1。因此,您会因为尝试访问堆栈的不存在的负索引而得到一个段错误:(

I don't know where I am doing a mistake. Is this the right way of writing a thread-safe stack class?

不,这不是正确的方法,互斥量只能保证锁定在同一个互斥量上的其他线程当前不能运行同一段代码。如果他们到达同一个部分,他们将被阻止进入该部分,直到互斥体被释放。但是您根本没有在对empty() 的调用 和其余调用之间进行锁定。一个线程到达empty(),锁定,获取值,然后释放,然后另一个线程可以自由进入和查询,并且很可能获得相同的值。是什么阻止了它稍后进入对 top() 的调用,又是什么阻止了第一个线程此时已经在同一个 pop() 之后?

在这些情况下,您需要仔细查看同步性方面需要保护的全部范围。这里打破的东西叫做atomicity,意思是“中间不能被切割”的属性。如你所见here it says原子性通常是通过互斥来强制执行的,”——就像通过使用互斥锁一样,就像您所做的那样。缺少的是 it was grained太精细了——“原子大小”操作太小了。您应该一直保护 empty()-top()-pop() 的整个序列,正如我们现在意识到的那样不能将三者中的任何一部分分开。在代码中,它看起来像是在 func() 中调用它,并且仅当它返回 true 时才打印到 cout:

bool safe_pop(int& value)
{
    std::lock_guard<std::mutex> lock{ mtx };

    if (stk.size() > 0)
    {
        value = stk[stk.size() - 1];
        stk.pop_back();
        return true;
    }

    return false;
}

不可否认,这并没有为这里的并行工作留下多少,但我想这是一个不错的并发练习。

关于c++ - 使用 Mutex 挂起程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52317607/

相关文章:

c++ - 对模板类使用 offsetof

c# - .Net Garbage 会收集一个未被引用但有一个正在工作的线程的对象吗?

c++ - 为多线程锁定一部分内存

java - 等待事件发生的队列

java - 生产者-消费者模型;存储缓冲区进入和退出时间

c++ - 由于安装(EXPORT "foo-targets"...)在导出集中多次包含目标 "foo",aws-cpp-sdk 在 Windows 构建期间失败

c++ - 在处理时写入数据 block - 由于硬件限制是否存在收敛值?

c++ - Xcode 和 C++ header

java - 如何从多线程类中正确获取静态变量?

java - 生产者/消费者中的信号量与互斥体