关于c++:C并发与多线程-5互斥量概念用法死锁演示及详解

25次阅读

共计 4963 个字符,预计需要花费 13 分钟才能阅读完成。

互斥量的基本概念

  • 临界资源:每次只容许一个线程进行拜访(读 / 写)的资源
  • 线程间的互斥(竞争):多个线程在同一时刻都须要拜访临界资源
  • mutex 类(互斥量)是一把线程锁,保障线程间的互斥

    • 利用线程锁可能保障临界资源的平安

互斥量的用法

lock

  • mutex.lock()

    • 当锁闲暇时,获取并继续执行
    • 当锁被获取时,阻塞并期待锁开释
  • mutex.unlock()

    • 开释锁(同一把锁的获取和开释必须在同一线程中成对呈现)

    如果 mutex 在调用 unlock() 时处于闲暇状态,那么程序的行为是未定义的

lock_guard

  • 在 lock_guard 对象结构时,传入的 mutex 对象(即它治理的 mutex 对象)会被以后线程锁住。
  • 在 lock_guard 对象被析构时,它所治理的 mutex 对象会主动解锁,不再须要手动调用 lock 和 unlock 对 mutex 进行上锁和解锁操作;
  • lock_guard 对象并不负责 mutex 对象的生命周期,只是简化了上锁和解锁操作;
  • 这种采纳“资源分配时初始化”(RAII)办法来加锁、解锁, 防止了在临界区中因为抛出异样或 return 等操作导致没有解锁就退出的问题 。这极大的简化了编写 mutex 相干的异样解决代码。
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>

using namespace std;

class Handle {
public:
    void inMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {m_mutex.lock();                     // 留神这里!!!cout << "inMsgRevQueue() :" << i << endl;
            m_queue.push(i);

            m_mutex.unlock();                   // 留神这里!!!}
    }

    void outMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {lock_guard<mutex> lck(m_mutex);     // 留神这里!!!if (!m_queue.empty())
            {cout << "outMsgRevQueue() :" << m_queue.front() << endl;
                m_queue.pop();}
        }
    }

private:
    queue<int> m_queue;
    mutex m_mutex;
};

int main()
{
    cout << "main begin" << endl;

    Handle handler;

    thread th1(&Handle::inMsgRevQueue, std::ref(handler));
    thread th2(&Handle::outMsgRevQueue, std::ref(handler));

    th1.join();
    th2.join();

    cout << "main end" << endl;

    return 0;
}

输入:

......

outMsgRevQueue() : 98267
outMsgRevQueue() : 98268
outMsgRevQueue() : 98269
outMsgRevQueue() : 98270
main end

死锁

  • 概念

    • 线程间相互期待临界资源而造成彼此无奈持续向下运行
  • 产生死锁的条件

    • 零碎存在多个临界资源且临界资源不可抢占
    • 线程须要多个临界资源能力持续运行


死锁的演示

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>

using namespace std;

class Handle {
public:
    void inMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {m_mutex1.lock();
            m_mutex2.lock();

            cout << "inMsgRevQueue() :" << i << endl;
            m_queue.push(i);

            m_mutex2.unlock();
            m_mutex1.unlock();}
    }

    void outMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {m_mutex2.lock();
            m_mutex1.lock();

            if (!m_queue.empty())
            {cout << "outMsgRevQueue() :" << m_queue.front() << endl;
                m_queue.pop();}

            m_mutex1.unlock();
            m_mutex2.unlock();}
    }

private:
    queue<int> m_queue;
    mutex m_mutex1;
    mutex m_mutex2;
};

int main()
{
    cout << "main begin" << endl;

    Handle handler;

    thread th1(&Handle::inMsgRevQueue, std::ref(handler));
    thread th2(&Handle::outMsgRevQueue, std::ref(handler));

    th1.join();
    th2.join();

    cout << "main end" << endl;

    return 0;
}

输入:

inMsgRevQueue() : 63
inMsgRevQueue() : 64
inMsgRevQueue() : 65
inMsgRevQueue() : 66
inMsgRevQueue() : 67

不再输入 ...

死锁的防止

死锁的个别解决方案

  • 对所有的临界资源都调配一个惟一的编号 (n1, n2, n3)
  • 对应的线程锁也调配同样的序号(m1, m2, m3)
  • 零碎中的每个线程依照严格递增(递加)的秩序申请资源

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>

using namespace std;

class Handle {
public:
    void inMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {m_mutex1.lock();
            m_mutex2.lock();

            cout << "inMsgRevQueue() :" << i << endl;
            m_queue.push(i);

            m_mutex2.unlock();
            m_mutex1.unlock();}
    }

    void outMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {m_mutex1.lock();
            m_mutex2.lock();

            if (!m_queue.empty())
            {cout << "outMsgRevQueue() :" << m_queue.front() << endl;
                m_queue.pop();}

            m_mutex2.unlock();
            m_mutex1.unlock();}
    }

private:
    queue<int> m_queue;
    mutex m_mutex1;
    mutex m_mutex2;
};

int main()
{
    cout << "main begin" << endl;

    Handle handler;

    thread th1(&Handle::inMsgRevQueue, std::ref(handler));
    thread th2(&Handle::outMsgRevQueue, std::ref(handler));

    th1.join();
    th2.join();

    cout << "main end" << endl;

    return 0;
}

输入:

inMsgRevQueue() : 99995
inMsgRevQueue() : 99996
inMsgRevQueue() : 99997
inMsgRevQueue() : 99998
inMsgRevQueue() : 99999
main end

std::lock 函数模板

lock(mutex1, mutex2, ...); 一次锁定两个或多个锁,用于解决多个互斥量

  • 当全副锁闲暇时,获取并继续执行
  • 当全副锁未被获取时,阻塞并期待锁开释

    • 当只获取其中一部分锁,另一部分锁被其它线程锁定时,则开释以后已获取的锁,持续期待并从新尝试获取全副锁,以此避免死锁
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>

using namespace std;

class Handle {
public:
    void inMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {m_mutex1.lock();
            m_mutex2.lock();

            cout << "inMsgRevQueue() :" << i << endl;
            m_queue.push(i);

            m_mutex2.unlock();
            m_mutex1.unlock();}
    }

    void outMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {lock(m_mutex1, m_mutex2);       // 留神这里!!!if (!m_queue.empty())
            {cout << "outMsgRevQueue() :" << m_queue.front() << endl;
                m_queue.pop();}

            m_mutex2.unlock();
            m_mutex1.unlock();}
    }

private:
    queue<int> m_queue;
    mutex m_mutex1;
    mutex m_mutex2;
};

int main()
{
    cout << "main begin" << endl;

    Handle handler;

    thread th1(&Handle::inMsgRevQueue, std::ref(handler));
    thread th2(&Handle::outMsgRevQueue, std::ref(handler));

    th1.join();
    th2.join();

    cout << "main end" << endl;

    return 0;
}

输入:

outMsgRevQueue() : 99599
outMsgRevQueue() : 99600
outMsgRevQueue() : 99601
outMsgRevQueue() : 99602
main end

std::lock_guard 的 std::adopt_lock 参数

应用 adopt_lock 结构的 unique_lock 和 lock_guard 对象在结构时不会锁定 mutex 对象,而是假设它已被以后线程锁定。

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>

using namespace std;

class Handle {
public:
    void inMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {m_mutex.lock();

            cout << "inMsgRevQueue() :" << i << endl;
            m_queue.push(i);

            m_mutex.unlock();}
    }

    void outMsgRevQueue()
    {for (int i=0; i<100000; ++i)
        {m_mutex.lock();

            lock_guard<mutex> lck(m_mutex, adopt_lock);     // 留神这里!!!if (!m_queue.empty())
            {cout << "outMsgRevQueue() :" << m_queue.front() << endl;
                m_queue.pop();}
        }
    }

private:
    queue<int> m_queue;
    mutex m_mutex;
};

int main()
{
    cout << "main begin" << endl;

    Handle handler;

    thread th1(&Handle::inMsgRevQueue, std::ref(handler));
    thread th2(&Handle::outMsgRevQueue, std::ref(handler));

    th1.join();
    th2.join();

    cout << "main end" << endl;

    return 0;
}

输入:

......

outMsgRevQueue() : 97515
outMsgRevQueue() : 97516
outMsgRevQueue() : 97517
outMsgRevQueue() : 97518
main end

正文完
 0