• C11线程管理:互斥锁


    1、概述

      锁类型

      c11提供了跨平台的线程同步手段,用来保护多线程同时访问的共享数据。
      std::mutex,最基本的 Mutex 类,独占的互斥量,不能递归使用。
      std::time_mutex,带超时的独占互斥量,不能递归使用。
      std::recursive_mutex,递归互斥量,不带超时功能。
      std::recursive_timed_mutex,带超时的递归互斥量。

      lock类型

      std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
      std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

      锁函数

      std::try_lock,尝试同时对多个互斥量上锁。
      std::lock,可以同时对多个互斥量上锁。
      std::unlock,解锁。

    2、独占互斥量std::mutex

      互斥量使用都很简单,接口用法也很简单。一般是通过lock()来阻塞线程,直到获取到互斥量为止。在获取互斥量完成之后,使用unlock()来解除互斥量的占用。lock()和unlock()必须成对出现。

      std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。

      lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

      unlock(), 解锁,释放对互斥量的所有权。

      try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

    #include <iostream>
    #include <thread>
    #include <chrono>
    #include <mutex>
    
    std::mutex g_lock;
    
    void vFunc()
    {
        g_lock.lock();
    
        std::cout << "entered thread:" << std::this_thread::get_id() << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "leave thread:" << std::this_thread::get_id() << std::endl;
    
        g_lock.unlock();
    }
    
    int main()
    {
        std::thread t(vFunc);
        std::thread t1(vFunc);
        std::thread t2(vFunc);
    
        t.join();
        t1.join();
        t2.join();
    
        return 0;
    }
    //输出结果
    entered thread:4420
    leave thread:4420
    entered thread:5288
    leave thread:5288
    entered thread:1432
    leave thread:1432

    3、std::lock_guard和std::unique_lock

      使用lock_guard和std::unique_lock可以简化lock/unlock的写法,同时也更安全,因为lock_guard使用了RAII技术,在构造分配资源,在析构释放资源,会在构造的时候自动锁定互斥量,在退出作用域之后进行析构自动解锁。所以不用担心没有解锁的情况,更加安全。

      std::lock_guard与 Mutex RAII 相关,方便线程对互斥量上锁,std::unique_lock与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制,它可以自由的释放mutex,而std::lock_guard需要等生命周期结束才能释放。

    void vFunc()
    {
        std::lock_guard<std::mutex> locker(g_lock);//出作用域自动解锁
        std::cout << "entered thread:" << std::this_thread::get_id() << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "leave thread:" << std::this_thread::get_id() << std::endl;
    }

    3、递归互斥锁std::recuisive_mutex

      同一个线程不能多次获取同一个独占互斥锁,一个线程多次获取同一个互斥锁发生死锁。

    //std::mutex mutex;
    
    void funcA()
    {
        std::lock_guard<std::mutex> lock(mutex);
        //do something
    }
    
    void funcB()
    {
        std::lock_guard<std::mutex> lock(mutex);
        //do something
    }
    
    void funcC()
    {
        std::lock_guard<std::mutex> lock(mutex);
        funcA();
        funcB();
    }
    
    int main()
    {
        //发生死锁,funcC已经获取,无法释放,funcA无法获取
        funcC(); 
        
        return 0;
    }

      为了解决死锁的问题,C11有了递归锁std::recursive_mutex,递归锁允许一个线程多次获取该锁。

    //std::recursive_mutex mutex;
    
    void funcA()
    {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        //do something
    }
    
    void funcB()
    {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        //do something
    }
    
    void funcC()
    {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        funcA();
        funcB();
    }
    
    int main()
    {
        //同一个线程可以多次获取同一互斥量,不会发生死锁
        funcC(); 
        
        return 0;
    }

      需要注意的是,尽量少用递归锁,原因如下:

      允许递归互斥容易放纵复杂逻辑的产生,从而导致一些多线程同步引起的晦涩问题;
      递归锁比非递归锁效率低;
      递归锁虽然可以在同一线程多次获取,但是获取次数过多容易发生问题,引发std::system错误。

    4、超时锁

      std::time_mutex是超时的独占锁,std::recursive_timed_mutex是超时的递归锁,主要用于在获取锁时增加超时等待功能,设置一个等待获取锁的时间,超时后做其他的事情。超时锁多了两个获取锁的接口,try_lock_for和try_lock_until,这两个接口用来获取互斥量的超时时间。

      try_lock_for 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

      try_lock_until函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

    #include <iostream>       // std::cout
    #include <chrono>         // std::chrono::milliseconds
    #include <thread>         // std::thread
    #include <mutex>          // std::timed_mutex
    
    std::timed_mutex mtx;
    
    void fireworks() {
        // waiting to get a lock: each thread prints "-" every 200ms:
        while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
            std::cout << "-";
        }
        // got a lock! - wait for 1s, then this thread prints "*"
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        std::cout << "*
    ";
        mtx.unlock();
    }
    
    int main()
    {
        std::thread threads[10];
        // spawn 10 threads:
        for (int i = 0; i<10; ++i)
            threads[i] = std::thread(fireworks);
    
        for (auto& th : threads) th.join();
    
        return 0;
    }
  • 相关阅读:
    Hamming Distance(随机算法)
    Difference Between Primes
    Pet(dfs)
    29. Divide Two Integers
    28. Implement strStr()
    25. Reverse Nodes in k-Group
    24. Swap Nodes in Pairs
    23. Merge k Sorted Lists
    22. Generate Parentheses
    19. Remove Nth Node From End of List
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/7966828.html
Copyright © 2020-2023  润新知