Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/138.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 使用互斥锁和手动锁定互斥锁的区别_C++_Multithreading_Qt - Fatal编程技术网

C++ 使用互斥锁和手动锁定互斥锁的区别

C++ 使用互斥锁和手动锁定互斥锁的区别,c++,multithreading,qt,C++,Multithreading,Qt,具体来说,在const成员函数中,如果我在其开头使用mutex.lock(),并且在返回之前使用mutex.unlock(),则在OpenMP循环中运行时会崩溃。但是如果我在函数的开头用一个QMutexLocker(&mutex)替换这两个调用,它就会运行得很顺利。 VisualStudio2010,Qt4.8。 我希望这两个代码是等效的,但它们显然不是。我错过了什么 编辑: 虽然这不会重现问题,但有一个小例子: class TileCache { public: bool fillBu

具体来说,在const成员函数中,如果我在其开头使用
mutex.lock()
,并且在返回之前使用
mutex.unlock()
,则在OpenMP循环中运行时会崩溃。但是如果我在函数的开头用一个
QMutexLocker(&mutex)
替换这两个调用,它就会运行得很顺利。 VisualStudio2010,Qt4.8。 我希望这两个代码是等效的,但它们显然不是。我错过了什么

编辑: 虽然这不会重现问题,但有一个小例子:

class TileCache
{
public:
    bool fillBuffer(const std::string& name) const {
        //QMutexLocker lock(&mCacheMutex);
        mCacheMutex.lock();
        auto ite = mCache.find(name);
        if(ite == mCache.end())
            mCache.insert(ite, std::make_pair(name, new unsigned char[1024]));
        // code here
        mCacheMutex.unlock();
        return true;
    }
private:
    mutable std::map<std::string, unsigned char*> mCache;
    mutable QMutex mCacheMutex;
};

int main(int argc, char *argv[])
{
    std::cout << "Test" << std::endl;
    TileCache cache;
#pragma omp parallel for shared(cache)
    for(int i = 0; i < 2048; ++i)
    {
        cache.fillBuffer("my buffer");
    }
    return 0;
}
class-TileCache
{
公众:
bool fillBuffer(const std::string&name)const{
//QMutexLocker锁(&mcacheutex);
mCacheMutex.lock();
auto ite=mCache.find(名称);
如果(ite==mCache.end())
insert(ite,std::make_pair(名称,新的无符号字符[1024]);
//代码在这里
mCacheMutex.unlock();
返回true;
}
私人:
可变std::map-mCache;
可变QMutex-mCacheMutex;
};
int main(int argc,char*argv[])
{

std::cout正如前面所说,如果您有一个具有多个出口点的函数,那么您需要编写
mutex.unlock()
到每个退出点。但是如果您使用互斥锁,则不必执行此操作。

从中可以看出,QMutexLocker在某种程度上是托管的,这意味着它将在您超出范围时自动神奇地解锁。如果您的const成员函数只有一个可能的返回路径,则任何一种方法都可以。但是,如果此函数ecomes更复杂,或者如果您稍后更改设计,我只会使用该类。

locker对象的优点是可以自动处理所有退出点,包括由异常引起的退出点

通常忘记退出点或异常,但只会使互斥锁锁定,症状是程序挂起

如果你有一个崩溃,那么问题就在其他地方,当你使用锁存器对象时,你看到问题消失的事实只是一个巧合(如果有一个崩溃,程序肯定是错误的),如果没有崩溃,你不能说程序是正确的……尤其是在C++语言这样的语言中有这样一个概念:“未定义的行为”)

编辑 另一个不明显的优点是,使用创建的locker对象作为第一条语句,可以保证在返回调用方之前,解锁将作为最后一件事发生。如果函数中创建了依赖互斥体的其他对象,则这会产生不同:

void ok_func() {
    Locker mylock(mymutex);
    Obj myobj; // constructor and destructor require the lock
}

void buggy_func() {
    lock(mymutex);
    Obj myobj; // constructor and destructor require the lock
    unlock(mymutex);
    // Bug: myobj instance will be destroyed without the lock
}


void workaround_func() {
    lock(mymutex);
    {   // nested scope needed
        Obj myobj; // constructor and destructor require the lock
    }
    unlock(mymutex);
}

好的,在遵循@6502的建议后发现了问题。首先是一个重现问题的小示例:

class TileCache
{
    struct Culprit
    {
        Culprit(int& n) : mN(n) { ++mN; }
        ~Culprit() { --mN; }
    private:
        int& mN;
    };
public:
    int& fillBuffer(const std::string& name) const {
        //QMutexLocker lock(&mCacheMutex);
        mCacheMutex.lock();
        auto ite = mCache.find(name);
        if(ite == mCache.end())
            ite = mCache.insert(ite, std::make_pair(name, 0));
        Culprit culprit(ite->second);
        unsigned char somebuffer[1];
        somebuffer[ (ite->second -1) * 8192 ] = 'Q';
        mCacheMutex.unlock();
        return ite->second;
    }
private:
    mutable std::map<std::string, int> mCache;
    mutable QMutex mCacheMutex;
};
int main(int argc, char *argv[])
{
    TileCache cache;
#pragma omp parallel for shared(cache) num_threads(2)
    for(int i = 0; i < 2048; ++i)
    {
        int& n = cache.fillBuffer("my buffer");
        if(n != 0) throw std::logic_error("Buff");
    }
    return 0;
}
但实际函数还有一些其他返回点,这会阻止此解决方案工作

博士 因此我了解到,是的,与手动调用lock()和unlock()相比,如果在该作用域内创建的对象的析构函数也应该受到保护,那么使用基于作用域的互斥锁是不等价的。在这种情况下,作用域锁将起作用,但手动调用不会起作用


非常感谢所有试图帮助我的人,我对此表示感谢。

一个非常小的代码片段,显示了问题所在。我再次运行了应用程序,这次启用了运行时检查。它显示了函数范围内声明的变量周围的堆栈损坏。手动锁定/解锁不是异常安全的。函数具有所有的“返回”“前面有unlock语句。我现在对堆栈损坏的问题感到困惑,而且QMutexLocker没有出现这种情况。现在已经晚了,我明天会进一步调查,并将结果写在这里。看起来就像是在调查QMutexLocker的代码时(我正在查看qt 4.2.2)现在,我认为它并不总是锁定或解锁。我无法真正理解它锁定或不锁定时背后的逻辑,但可能你在另一个线程中也有一个互斥体副本,因为它是另一个实例,所以永远不会重置。如果你在堆上分配互斥体会发生什么?毕竟,这是一些非triv对象的问题在函数作用域内创建的析构函数call在这些析构函数完成之前释放了互斥锁,造成了竞争条件。非常感谢您的评论,它让我调查了该方法的内部代码,最终找到了问题。@LeCoc:很高兴听到您发现了问题。事实上,这是使用locker对象的另一个不明显的优势。因为销毁是反向进行的构造顺序如果你在回车时创建了锁,你也可以保证在离开函数之前解锁(也就是说,在执行所有其他本地实例析构函数之后)。编辑答案以更清楚地显示这一点。。。
int& fillBuffer(....) const {
    mCacheMutex.lock();
    {
        auto ite = ....
        ...
    }
    mCacheMutex.unlock();
}