条件变量和#pragma pack bug

时间:2017-05-31 08:15:13

标签: c++ multithreading c++11 synchronization g++

我发现这段代码无法正常工作:

#pragma pack(push, 1)

class myclass {
protected:
    bool mycrasher[1]; // with mycrasher[4] it works!
    std::mutex mtx;
    std::condition_variable cv;

    void thread_func() {
        std::this_thread::sleep_for(std::chrono::seconds(1));

        std::chrono::milliseconds timeout(1000);
        std::unique_lock<std::mutex> l(mtx, std::defer_lock);
        while (true) {
            auto now = std::chrono::system_clock::now();
            l.lock();
            while (true) {
                std::cout << "Waiting..." << std::endl;
                auto result = cv.wait_until(l, now + timeout);
                std::cout << "Timed out..." << std::endl;
                if (result == std::cv_status::timeout)
                    break;
            }
            l.unlock();
        }
    }   

public:
    myclass() { 
        std::lock_guard<std::mutex> l(mtx);
        std::thread *t = new std::thread(&myclass::thread_func, this);
        t->detach();
    };

    void start() {
        std::cout << "myclass started." << std::endl;
        std::cout << "sizeof(std::mutex) = " << sizeof(std::mutex) << std::endl;
        std::cout << "sizeof(std::condition_variable) = " << sizeof(std::condition_variable) << std::endl;
    }
};
#pragma pack(pop)

int main() {
    myclass x;
    x.start();
    std::this_thread::sleep_for(std::chrono::seconds(60));
}

我希望代码在cv.wait_until通话中等待一秒钟,然后重复,但只是在通话中挂起。如果我删除#pragma指令,问题(直观地)就会消失,因为我正在打包互斥锁和CV。但是,当我运行此代码时,我得到:

myclass started.
sizeof(std::mutex) = 40
sizeof(std::condition_variable) = 48

有或没有pragma,所以包装似乎不是真正的问题。

此外,我发现如果我将mycrasher变量与4字节边界对齐,问题也会消失。同样,如果我在std::condition_variable cv声明后移动变量,问题就会消失,但在std::mutex mtxstd::condition_variable cv之间移动时,它仍然存在。

当CV未正确对齐时,为什么代码段会在cv.wait_until来电时挂起?预计性能会受到影响,但不是普通的失速。

在Debian 8系统上用g ++ 4.9.2和g ++ 6.3重现。

1 个答案:

答案 0 :(得分:1)

不要使用包装,你不应该对具有互斥和条件变量的结构这样做。如果您认为自己确实需要,那么您的方法就会出现问题,因为您通常不应该创建许多这些对象,并且不应该在那里打包。

作为一种简单的解决方法,请将mycrasher放在mtxcv下方

#pragma pack(push, 1)
class myclass
{
protected:
    std::mutex mtx;
    std::condition_variable cv;
    bool mycrasher[1];

    void thread_func();
public:
    myclass();
    void start();
};
#pragma pack(pop)

由于mtxcv最终会出现奇数地址而某些使用这些内部代码的内部代码不同,因此它最有可能在启用打包的情况下无法正常工作期望。在某些平台上(例如ARM)可能会崩溃。

作为旁注,您的代码不仅存在对齐问题,还会泄漏myclass构造函数内的堆上的线程对象。