在C ++ 11多线程中,lock_guard的意外行为

时间:2015-10-28 09:56:58

标签: c++ multithreading c++11

以下是展示lock_guard

的意外行为的示例
#include <iostream>
#include <thread>
#include <mutex>


class OddEven
{
private:
        static const int max = 400;
        std::mutex mut;
public:
        OddEven(){}
        OddEven(const OddEven& oddeven)
        {
                //std::lock_guard<std::mutex> lk(oddeven.mut);
        }
        void printEven()
        {
                std::lock_guard<std::mutex> lk(mut);
                for(int i=0;i<max;i++)
                {
                        if (i%2 == 0)
                                std::cout<<i;
                        else
                                std::cout<<" ";
                }
                std::cout<<std::endl;
        }
        void printOdd()
        {
                std::lock_guard<std::mutex> lk(mut);
                for(int i=0;i<max;i++)
                {
                        //std::cout<<(((i%2)!=0)?i:' ');
                        if (i%2 != 0)
                                std::cout<<i;
                        else
                                std::cout<<" ";
                }
                std::cout<<std::endl;
        }
};

int main()
{
        OddEven oddeven;
        std::thread t1(&OddEven::printEven,oddeven);
        std::thread t2(&OddEven::printOdd,oddeven);
        t1.join();
        t2.join();
        return 0;

}

预期行为:偶数比奇数或反之亦然

3 个答案:

答案 0 :(得分:5)

问题不在于lock_guard,而在于threadthread将参数复制/移动到线程私有且可由线程访问的某个位置。由于您提供了左值,因此您的oddeven对象正在被复制,因此您最终会在两个不同的mutex es上运行两个不同的对象。

编译器会保护您免受此攻击,因为默认情况下,由于成员(oddEven)不可复制且不可移动,因此mutex类是不可复制的且不可移动的。但是通过编写自己的拷贝构造函数,您可以绕过这种保护。不要这样做,并考虑编译器给你的错误。

如何正确地做到这一点:

传递包含在std::ref中的参数或使用lambda

std::ref

OddEven oddeven;
std::thread t1(&OddEven::printEven, std::ref(oddeven));
std::thread t2(&OddEven::printOdd, std::ref(oddeven));
t1.join();
t2.join();

拉姆达

OddEven oddeven;
std::thread t1([&oddeven] { oddeven.printEven(); });
std::thread t2([&oddeven] { oddeven.printOdd(); });
t1.join();
t2.join();

答案 1 :(得分:4)

看起来您添加了一个复制构造函数来关闭编译器无法复制OddEven。但是编译器试图帮助你。

当您创建线程时,您将为两个线程提供自己的OddEven对象,每个对象都有自己的互斥锁。因此,锁定什么都不做。

编译器可能告诉你它在创建线程时无法复制OddEven,因为它无法生成复制构造函数,因为互斥锁是不可复制的。但是,不是要问为什么在那里复制了对象,而是创建了一个复制构造函数,它为新对象提供了自己的互斥锁。

解决方案是删除复制构造函数并使用oddevenstd::ref对象包装在调用中,以便传递对单个对象的引用,而不是每个线程获得自己的对象。

作为旁注,您不应将代码作为外部链接发布,您应该将其内联发布。更容易回答。

答案 2 :(得分:0)

std::thread t1(&OddEven::printEven,oddeven);
std::thread t2(&OddEven::printOdd,oddeven);

电话订单不可预测或偶数或奇数。需要添加同步事件:

printEven(){
  ...

 ...
 SetEvent(EvenCalculationDoneEvent)
} 
printOdd(){...
 WaitForSingleObject(EvenCalculationDoneEvent);
....
}

std :: mutex mut - 同步控制台输出,而不是计算顺序 很容易检查这样:

std::thread t1(&OddEven::printEven,oddeven);
Sleep(100);
std::thread t2(&OddEven::printOdd,oddeven);