#include<string>
#include<iostream>
#include<mutex>
#include<shared_mutex>
#include<thread>
#include<chrono>

using namespace std;

/*
std::mutex,基本的 mutex 类
std::recursive_mutex,支持同一个线程递归访问的 mutex 类
std::time_mutex,支持时间设定的 mutex 类
std::recursive_timed_mutex,支持递归和时间设定的 mutex 类
std::shared_mutex,允许独占和共享两个级别的锁 mutex 类(C++ 17 提供)
std::shared_timed_mutex,允许独占和共享两个级别的锁的带时间设定的 mutex 类(C++ 14 提供)
*/

void testMutex()
{
    mutex mtx;
    auto thfun = [&](int v){
        for(int i=0;i<v;i++)
        {
            mtx.lock();//阻塞直到获取到锁,
            //try_lock()用于尝试获取互斥锁,当取不到的时返回false,不会阻塞
            //                while(!mtx.try_lock())
            //                {
            //                    std::this_thread::sleep_for(std::chrono::milliseconds(200));
            //                }

            cout<<"threadid="<<std::this_thread::get_id()<<":"<<i<<endl;//io输出不加锁输出混乱
            mtx.unlock();
        }
    };
    thread t1(thfun, 3);
    thread t2(thfun, 3);
    thread t3(thfun, 3);
    t1.join();
    t2.join();
    t3.join();
}

void testTimedMutex()
{
    timed_mutex mtx;//相对于mutex多了,try_lock_for() 和 try_lock_until() 两个函数用于时间控制
    auto thfun = [&](){
        bool result = mtx.try_lock_for(chrono::seconds(2));//主线程已经锁住,这里会阻塞2s超时返回false
        //        bool result = mtx.try_lock_until(chrono::system_clock::now()+chrono::seconds(2));//等待直到当前时间2s后超时
        cout<<std::this_thread::get_id()<<",result="<<result<<endl;
        if(result)
        {
            this_thread::sleep_for(chrono::seconds(2));
            mtx.unlock();
        }
    };
    mtx.lock();//这里锁住
    thread t1(thfun);
    t1.join();
}

void testRecursiveMutex()
{
    recursive_mutex rmtx;
    int sum = 0;
    auto sumfun = [&](int v){
        for(int i=0;i<v;i++)
        {
            rmtx.lock(); //try_lock()与mutex一样,当取不到的时返回false,不会阻塞,但同一线程已经lock的也能正常获取到返回true
            sum+=i;
            rmtx.unlock();
        }
    };
    auto thfun = [&](int v){
        rmtx.lock();//第一次锁,
        sumfun(v);//递归调用的场景,第二次锁,如果是mutex锁进入会直接死锁,recursive_mutex同一线程不会死锁
        cout<<"threadid="<<std::this_thread::get_id()<<":"<<sum<<endl;
        rmtx.unlock();
    };
    thread t1(thfun, 3);
    t1.join();
}

void testTimeRecursiveMutex()
{
    recursive_timed_mutex rmtx;//相对于recursive_mutex多了,try_lock_for() 和 try_lock_until() 两个函数用于时间控制
    int sum = 0;
    auto thfun = [&](){
        bool result = rmtx.try_lock_for(chrono::seconds(2));//主线程已经锁住,这里会阻塞2s超时返回false
        cout<<std::this_thread::get_id()<<",result="<<result<<endl;

        result = rmtx.try_lock_for(chrono::seconds(2));//主线程已经锁住,这里会阻塞2s超时返回false
        cout<<std::this_thread::get_id()<<",result="<<result<<endl;

        cout<<"threadid="<<std::this_thread::get_id()<<":"<<sum<<endl;
        rmtx.unlock();
        rmtx.unlock();
    };
    rmtx.lock();//这里锁住
    thread t1(thfun);
    t1.join();
}

void testSharedMutex()
{
    //std::shared_mutex 设置了两种级别的锁控制,一种是独占锁控制,一种是共享锁控制,对于写操作,用独占锁,对于读操作用共享锁
    //独占锁控制的方法是:lock()、unlock() 和 try_lock() 方法,它们的行为和 mutex 是一样的
    //共享锁控制的方法是:lock_shared()、try_lock_shared() 和 unlock_shared()
    //一个 shared_mutex,如果被 lock() 方法独占锁了,那么其他线程再对这个互斥体无论是 lock(),还是 lock_shared(),都将阻塞,直到互斥体被 unlock
    //如果是被lock_shared()上锁,则其他线程中的lock_shared()不会阻塞,如果其他线程是 lock()则会被挂起,直到所有获取共享锁的线程都unlock_shared()才能继续上独占锁
    std::shared_mutex smutex;
    auto reader = [&]() {
        smutex.lock_shared();
        std::cout << this_thread::get_id()<<",read" << std::endl;
        smutex.unlock_shared();
    };
    auto writer = [&]() {
        smutex.lock();
        std::cout << "write" << std::endl;
        this_thread::sleep_for(chrono::seconds(1));
        smutex.unlock();
    };
    int count = 8;
    std::thread rw_threads[count];
    for(int i = 0; i < count; i++)     {
        if((i % 4) == 0)
            rw_threads[i] = std::thread(writer);
        else
            rw_threads[i] = std::thread(reader);
    }
    for(auto& t : rw_threads)
        t.join();
}

void testSharedTimedMutex()
{
    //std::shared_timed_mutex相对于std::shared_mutex,分别多了可以设置超时的方法
    //独占锁控制的方法多了:try_lock_for() 和 try_lock_until()
    //共享锁控制的方法多了:try_lock_shared_for() 和 try_lock_shared_until()
    std::shared_timed_mutex smutex;
    auto reader = [&]() {
        smutex.try_lock_shared_for(chrono::seconds(3));
        std::cout << this_thread::get_id()<<",read" << std::endl;
        smutex.unlock_shared();
    };
    auto writer = [&]() {
        smutex.try_lock_for(chrono::seconds(3));
        std::cout << "write" << std::endl;
        this_thread::sleep_for(chrono::seconds(1));
        smutex.unlock();
    };
    int count = 8;
    std::thread rw_threads[count];
    for(int i = 0; i < count; i++)     {
        if((i % 4) == 0)
            rw_threads[i] = std::thread(writer);
        else
            rw_threads[i] = std::thread(reader);
    }
    for(auto& t : rw_threads)
        t.join();
}
/*
五种类型的守护锁分别是:
std::lock_guard,方便线程对各种 mutex 互斥量上锁
std::unique_lock,方便线程对各种 mutex 互斥量上锁,提供更灵活的控制手段
std::shared_lock,配合 shared mutex 互斥量(C++ 14)
std::scoped_lock,方便线程对各种 mutex 互斥量上锁(C++ 17)
*/
void test_lock_guard()
{
    /*
        std::lock_guard‌:
        1.构造时自动lock()
        2.析构时自动unlock()
        3.禁止拷贝构造和赋值操作(不能复制或移动)
        4.带adopt_lock参数的构造函数,不会在构造时加锁,通常用于当互斥锁已经被当前线程加锁,但需要lock_guard在作用域结束时自动解锁的情况
        5.构造时只能lock(),无法使用try_lock、lock_shared、try_lock_shared_for等上锁
    */
    {
        std::mutex amtuex;
        std::lock_guard<std::mutex> guard(amtuex);

        std::mutex amtuex2;
        amtuex2.lock();
        std::lock_guard<std::mutex> guard1(amtuex2,adopt_lock);//adopt_lock参数构造时不会在构造时加锁,但析构时自动解锁

        std::recursive_mutex rmutex;
        std::lock_guard<std::recursive_mutex> guard2(rmutex);

        std::timed_mutex tmutex;
        std::lock_guard<std::timed_mutex> guard3(tmutex);

        std::recursive_timed_mutex rtrmutex;
        std::lock_guard<std::recursive_timed_mutex> guard4(rtrmutex);

        std::shared_mutex smutex;
        std::lock_guard<std::shared_mutex> guard5(smutex);

        std::shared_timed_mutex stmutex;
        std::lock_guard<std::shared_timed_mutex> guard6(stmutex);
    }
}

void test_unique_lock()
{
    /*
    默认:构造时调用 mutex.lock()(与 lock_guard 一致);
    std::defer_lock:延迟上锁(构造时不上锁,后续手动调用 lock());
    std::try_to_lock:尝试上锁(成功返回 true,失败不阻塞,需通过 owns_lock() 判断);
    std::adopt_lock:适配已上锁的互斥量(与 lock_guard 一致)
    */
    //默认与 lock_guard 一致
    {
        std::mutex amtuex;
        std::unique_lock<std::mutex> unique(amtuex);

        std::mutex amtuex2;
        amtuex2.lock();
        std::unique_lock<std::mutex> unique1(amtuex2,adopt_lock);//adopt_lock参数构造时不会在构造时加锁,但析构时自动解锁

        std::recursive_mutex rmutex;
        std::unique_lock<std::recursive_mutex> unique2(rmutex);

        std::timed_mutex tmutex;
        std::unique_lock<std::timed_mutex> unique3(tmutex);

        std::recursive_timed_mutex rtrmutex;
        std::unique_lock<std::recursive_timed_mutex> unique4(rtrmutex);

        std::shared_mutex smutex;
        std::unique_lock<std::shared_mutex> unique5(smutex);

        std::shared_timed_mutex stmutex;
        std::unique_lock<std::shared_timed_mutex> unique6(stmutex);
    }
    std::mutex mtx;
    {
        std::unique_lock<std::mutex> lock(mtx);
        //         std::unique_lock<std::mutex> lock2(lock);//unique_lock不可复制,但可通过std::move() 转移所有权
        std::unique_lock<std::mutex> lock2(std::move(lock));
    }
    {
        // 延迟上锁:构造时不上锁
        std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
        // 手动上锁(此时才真正获取锁)
        lock.lock();//unique_lock的lock会先调用锁的lock然后设置owns = true,所以其析构时能自动解锁
        // 手动解锁(提前释放锁,提高并发效率)
        lock.unlock();
        // 可多次调用
        lock.lock();
        // 手动解锁(不手动调用,其能析构时也能自动解锁)
        lock.unlock();
    }
    {
        //mtx.lock();构造前加上锁,try_to_lock就会失败,不能成功获取锁owns_lock返回false,此时析构时不会自动解锁
        std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
        if (lock.owns_lock()) { // 判断是否成功获取锁
            std::cout << "Successfully locked, access shared resource"<<endl;
        } else {
            std::cout << "Failed to lock, do other things"<<endl;
        }
        // 析构时(若持有锁,即owns_lock为true)才会自动解锁,否则不会自动解锁
    }
    {
        mtx.lock();
        //适配已上锁的互斥量(与 lock_guard 一致),adopt_lock参数构造时不会在构造时加锁,但析构时自动解锁
        std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);
        std::cout << "adopt_lock"<<endl;
        //析构时会自动解锁
    }
    /*
        配合条件变量condition_variable使用
        std::condition_variable 的 wait() 必须传入 unique_lock,因为 wait() 会:
        自动解锁 unique_lock 对应的互斥量;
        阻塞当前线程,等待唤醒;
        被唤醒后,自动重新上锁 unique_lock
    */
    {
        std::mutex mtx;
        std::condition_variable cv;
        bool ready = false; // 共享条件
        auto worker = [&]() {
            std::unique_lock<std::mutex> lock(mtx);
            std::cout << "Worker start"<<endl;
            // 等待条件满足(自动解锁,阻塞;被唤醒后重新上锁)
            cv.wait(lock, [&]{ return ready; });//wait()必须传入 unique_lock
            // 条件满足,执行临界区
            std::cout << "Worker"<<endl;
        };
        std::thread t(worker);
        {
            this_thread::sleep_for(chrono::seconds(1));//使wait先执行,解锁,等待唤醒,配合后面测试枷锁后notify_one,浮现wait卡死现象
            std::lock_guard<std::mutex> lock(mtx); // 简单场景用 lock_guard 即可
            ready = true;
        } // 析构时解锁
        //std::lock_guard<std::mutex> lock(mtx);这里如果再上锁,wait里面被唤醒后无法重新lock导致卡死
        std::cout<<"notify_one"<<endl;
        cv.notify_one(); // 唤醒等待的线程
        t.join();
    }
}

void test_shared_lock()
{
    /*
        1.只能用于管理共享锁shared_mutex,shared_timed_mutex
        2.析构时如果持有锁会调用unlock_shared()
    */
    {
        std::shared_mutex smutex;
        std::shared_lock<std::shared_mutex> share(smutex);//构造时默认调用smutex.lock_shared()

        std::shared_timed_mutex stmutex;
        std::shared_lock<std::shared_timed_mutex> share1(stmutex);

        //std::shared_lock<std::shared_mutex> lock2(share);//shared_lock不可复制,但可通过std::move() 转移所有权
        std::shared_lock<std::shared_mutex> share2(std::move(share));
    }
    std::shared_mutex smutex; // reader/writer mutex
    {
        // 延迟上锁:构造时不上锁
        std::shared_lock<std::shared_mutex> lock(smutex, std::defer_lock);//构造时不调用任何lock函数,owns_lock===false
        // 手动上锁(此时才真正获取锁)
        lock.lock();//lock会先调用锁的shared_lock然后设置owns = true,所以其析构时能自动解锁
         // 手动解锁(不手动调用,其能析构时也能自动解锁)
        lock.unlock();
    }
    {
        //        smutex.lock();//构造前加上独占锁,try_to_lock就会失败,不能成功获取锁owns_lock返回false,此时析构时不会自动解锁
        //         smutex.lock_shared();//构造前加上非独占锁,try_to_lock就会成功获取锁owns_lock返回ture,此时析构时不会自动解锁
        std::shared_lock<std::shared_mutex> lock(smutex, std::try_to_lock);//构造时调用try_lock_shared
        if (lock.owns_lock()) { // 判断是否成功获取锁
            std::cout << "Successfully locked, access shared resource"<<endl;
        } else {
            std::cout << "Failed to lock, do other things"<<endl;
        }
        // 析构时(若持有锁,即owns_lock为true)才会自动解锁,否则不会自动解锁
    }
    {
        smutex.lock_shared();//这里不调用smutex.lock(),否则shared_lock析构无法自动解锁
        //适配已上共享锁的互斥量,adopt_lock参数构造时不会在构造时加锁,但析构时自动unlock_shared()解锁
        std::shared_lock<std::shared_mutex> lock(smutex, std::adopt_lock);
        std::cout << "adopt_lock"<<endl;
        //析构时(若持有锁,即owns_lock为true)会自动解锁,unlock_shared()
    }

    std::shared_timed_mutex stmutex; // reader/writer mutex
    {
        auto time = chrono::system_clock::now()+chrono::seconds(5);
        std::shared_lock<std::shared_timed_mutex> lock(stmutex,time);//构造时调用try_lock_shared_until
        std::cout << "try_lock_shared_until"<<endl;
        //析构时(若持有锁,即owns_lock为true)会自动解锁,unlock_shared()
    }
    {
        auto dur = chrono::seconds(5);
        //适配已上共享锁的互斥量,adopt_lock参数构造时不会在构造时加锁,但析构时自动unlock_shared()解锁
        std::shared_lock<std::shared_timed_mutex> lock(stmutex, dur);//构造时调用try_lock_shared_for
        std::cout << "try_lock_shared_for"<<endl;
        //析构时会自动解锁,unlock_shared()
    }
}

void test_scoped_lock()
{
    /*
    支持同时持有多个互斥量(可以是不同类型)并在构造时一次性、无死锁地上锁,离开作用域自动按逆序解锁
    1.构造时自动(内部算法动态决定多个互斥量的锁定顺序)lock(),不会出现部分锁定状态‌:要么同时锁定所有互斥锁,要么都不锁定
    2.析构时自动加锁顺序的逆序unlock()
    3.禁止拷贝构造和赋值操作(不能复制或移动)
    4.构造时若上锁过程中抛出异常,已获取的锁会被释放(std::lock 保证),对象构造失败不泄露锁
    5.切记:传入同一个互斥量会自锁死
    */
    std::mutex mutex;
    std::mutex mutex2;
    std::recursive_mutex rmutex;
    std::shared_mutex smutex;
    {
        //构造时按照内部算法决定顺序对mutex,rmutex,smutex进行lock
        std::scoped_lock<std::mutex,std::recursive_mutex,std::shared_mutex> scoped(mutex,rmutex,smutex);
        //析构时按照上锁顺序的逆序进行unlock
    }
    {
        mutex2.lock();
        cout<<"lock ok"<<endl;
        thread t([&](){
            this_thread::sleep_for(chrono::seconds(2));
            mutex2.unlock();
        });
        //锁定mutex2失败时,此时mutex已被锁定,它会‌自动释放mutex,进入短暂的等待状态,再次尝试同时锁定两个互斥锁,重复此过程‌,直到成功锁定所有互斥锁或发生异常
        std::scoped_lock<std::mutex,std::mutex> scoped(mutex,mutex2);//会阻塞2s,直到mutex释放锁,且在
        cout<<"scoped_lock ok"<<endl;
        t.join();
    }
    /*
     1.锁定顺序‌不是‌按照参数传入顺序,而是采用一种特殊的死锁避免算法来确定锁定顺序
     2.确保所有线程按照相同的逻辑顺序加锁,从而从根本上防止死锁发生
    {
          std::mutex mtx1, mtx2, mtx3;
          thread t([&](){
              cout<<"t1"<<endl;
              std::scoped_lock<std::mutex,std::mutex,std::mutex>(mtx3, mtx2, mtx1);//不会产生死锁
              cout<<"t1 lock"<<endl;
          });
          thread t2([&](){
              cout<<"t2"<<endl;
              std::scoped_lock<std::mutex,std::mutex,std::mutex>(mtx1, mtx2, mtx3);//不会产生死锁
              cout<<"t2 lock"<<endl;
          });
          t.join();
          t2.join();
      }
    */
}

int main()
{
    testMutex();
    testTimedMutex();
    testRecursiveMutex();
    testTimeRecursiveMutex();
    testSharedMutex();
    testSharedTimedMutex();
    test_lock_guard();
    test_unique_lock();
    test_shared_lock();
    test_scoped_lock();
    return 0;
}

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐