简体   繁体   English

使用Mutex挂起程序

[英]Use of Mutex hangs the program

I am trying to learn concurrency programming in C++. 我正在尝试学习C ++中的并发编程。

I implemented a basic stack class with push(), pop(), top() and empty() methods. 我用push(),pop(),top()和empty()方法实现了一个基本的堆栈类。

I created two threads and both of them will try to access the top element and pop it until the stack gets empty. 我创建了两个线程,它们都将尝试访问顶部元素并将其弹出,直到堆栈变空。

First, I tried to implement it without using a mutex, and the output was gibberish and in the end resulting in segfault , which was expected as the operations were not atomic so the data race was inevitable. 首先,我尝试在不使用互斥锁的情况下实现它,并且输出乱码,最后导致segfault ,这是预期的,因为操作不是原子操作,因此不可避免的是数据竞争。

So I tried to implement it with a mutex, and the program was hanging without even giving any output because of not unlocking the mutex. 因此,我尝试使用互斥锁来实现它,并且由于未解锁互斥锁,该程序甚至没有给出任何输出就挂起了。

Now, I have used a mutex locking+unlocking sequence properly, my program is giving correct output as desired, But afterwards the program is hanging -- could be possibly due to threads are still in execution or control is not reaching to the main thread?. 现在,我正确地使用了互斥锁锁定+解锁序列,我的程序正在提供所需的正确输出,但是此后程序挂起了-可能是由于线程仍在执行中或控制未到达主线程? 。

#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;
}

I figured because once the stack was empty, I was not unlocking the mutex. 我想是因为一旦堆栈为空,我就不会解锁互斥锁。 So when I uncomment the commented line and run it, it gives gibberish output and segfault. 因此,当我取消注释并运行注释行时,它将产生乱码输出和段错误。

I don't know where I am doing a mistake. 我不知道我在哪里做错。 Is this the right way of writing a thread-safe stack class? 这是编写线程安全堆栈类的正确方法吗?

One error is that MyStack::top and MyStack::empty it does not unlock the mutex. 一个错误是MyStack::topMyStack::empty不能解锁互斥锁。

Use std::lock_guard<std::mutex> to unlock the mutex automatically and eliminate the risk of such accidental dead-locks. 使用std::lock_guard<std::mutex>自动解锁互斥锁,并消除发生此类意外死锁的风险。 Eg: 例如:

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

And it probably needs to lock the mutex in MyStack::push as well. 而且它可能还需要将互斥锁锁定在MyStack::push中。


Another error is that the locking at the method level is too fine grained and empty() followed by top() and pop() is not atomic. 另一个错误是方法级别的锁定太细,并且在top()pop()之后的empty()不是原子的。

Possible fixes: 可能的修复:

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

    bool try_pop(int* result) {
        bool popped;
        {
            std::lock_guard<std::mutex> lock(mtx);
            if((popped = !stk.empty())) {
                *result = stk.back();
                stk.pop_back();
            }
        }
        return popped;
    }
};

void func(MyStack& ms, const std::string& s)
{
    for(int top; ms.try_pop(&top);) {
        std::lock_guard<std::mutex> l(a_mtx);
        cout << s << " " << top << "\n";
    }
}

it gives gibberish output and segfault. 它提供了混乱的输出和段错误。

It will still potentially give you segfault under the current synchronization scheme, even if you go with the suggested RAII style locking like this: 仍然将有可能给你段错误在当前的同步方案,即使你去与建议的RAII风格锁是这样的:

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;
}

as you are not taking care of the race-condition arising between two subsequent calls to these methods by different threads. 因为您不关心不同线程在两次后续调用这些方法之间产生竞争条件 For example, think of what happens when the stack has one element left and one thread asks if it's empty and gets a false and then you have a context switch and the other thread gets the same false for the same question. 例如,想一想当堆栈中剩下一个元素并且一个线程询问它是否为空并得到一个false ,然后您有一个上下文切换而另一个线程对相同的问题得到相同的false ,会发生什么。 So they're both racing for that top() and pop() . 因此, 他们都在争夺top()pop() While the first one already pops it and then the other one tries to top() it will do so in a situation where stk.size() - 1 yields -1 . 虽然第一个已经弹出它,然后另一个试图top()它会在stk.size() - 1产生-1的情况下这样做。 Hence, you get a segfault for trying to access a non-existing negative index of the stack : ( 因此,你会得到一个segfault对试图访问堆栈的一个不存在的负指数:(

I don't know where I am doing a mistake. 我不知道我在哪里做错。 Is this the right way of writing a thread-safe stack class? 这是编写线程安全堆栈类的正确方法吗?

No, this is not the right way, the mutex only guarantees that other threads locking on the same mutex cannot be currently running this same section of the code. 不,这不是正确的方法,互斥锁只能保证锁定在该互斥锁上的其他线程当前无法运行该代码的同一部分。 If they get to the same section, they're blocked from entering it until the mutex is released. 如果他们进入同一部分,则在释放互斥锁之前,他们将无法进入。 But you are not locking at all between the call to empty() and the rest of the calls. 但是您根本没有在empty()的调用与其余调用之间锁定 One thread gets to empty() , locks, gets the value, then releases it, and then the other thread is free to enter and query and may well get the same value. 一个线程进入empty() ,锁定,获取值,然后释放它,然后另一个线程可以自由输入和查询,并且很可能获得相同的值。 What's preventing it later to enter your call to top() , and what's preventing the first thread to already be after that same pop() at that time? 是什么阻止了以后再输入对top()调用,又是什么阻止了第一个线程当时已经在同一pop()之后呢?

In these scenarios you need to be careful to see the full scope of what needs protection in terms of synchronicity. 在这些情况下,您需要小心以同步方式查看需要保护的内容的全部范围。 The thing broken here is called atomicity , which means the property of "not being able be cut in middle". 此处被破坏的事物称为原子性 ,它表示“不能在中间被切割”的属性。 As you can see here it says that " Atomicity is often enforced by mutual exclusion, " -- as in by using mutexes, like you did. 正如您在此处看到的那样它说 :“ 原子性通常是通过互斥来实现的”,就像使用互斥锁一样,就像您所做的那样。 What was missing is that it was grained too finely -- the "size of the atomic" operation was too small. 缺少的是它的粒度太细了–“原子大小”操作太小。 You should have been protecting the entire sequence of empty() - top() - pop() as a whole, as we now realize we cannot separate any piece out of the three. 您应该一直保护empty() - top() - pop()的整个序列,因为我们现在意识到我们无法将这三个部分分开。 In code it could look something like calling this inside of func() and printing to cout only if it returned true : 在代码中,它看起来像是在func()内部调用此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;
}

Admittedly, this doesn't leave much for the parallel work here, but I guess it's a decent exercise in concurrency. 诚然,这对于这里的并行工作并没有多大作用,但是我想这是并发方面的一个不错的练习。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM