C++ 11锁的应用

mutex系列类(四种)

  • std::mutex:基本的 mutex 类。
  • std::recursive_mutex:递归 mutex 类。
  • std::time_mutex:定时 mutex 类。
  • std::recursive_timed_mutex:定时递归 mutex 类。

lock 类(两种)

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

其他类型

  • std::once_flag
  • std::adopt_lock_t
  • std::defer_lock_t
  • std::try_to_lock_t

函数

  • std::try_lock,尝试同时对多个互斥量上锁。
  • std::lock,可以同时对多个互斥量上锁。
  • std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。

std::mutex

基本的 std::mutex 是 C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁。而 std::recursive_lock 则可以递归地对互斥量对象上锁。

std::mutex 的成员函数:

  • 构造函数:·std::mutex ·不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
  • lock():调用线程将锁住该互斥量。线程调用该函数会发生下面3种情况:
    • 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock 之前,该线程一直拥有该锁;
    • 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住,直到 mutex 被释放;
    • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
  • unlock():释放对互斥量的所有权。
  • try_lock():尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面3种情况:
    • 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
    • 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。
    • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

使用

mutex的lock和unlock

  1. lock()和unlock()要成对使用,不能重复上锁和解锁。本质就是lock~unlock之间的程序(数据)不会同时调用、修改。
#include
#include 
#include 
#include 
std::mutex g_mutex;
int g_count = 0;


void counter(){
     


    for(int i = 0; i < 100000; i++){
     
        g_mutex.lock();
        g_count++;
        // 前面代码如有异常,unlock 就调不到了。
        g_mutex.unlock();
    }
}

int main()
{
     
    std::thread threads[4];
    for(int i = 0; i < 4; i++){
     
        threads[i] = std::thread(counter);
    }

    for (auto& th : threads){
     
        th.join();
    }
    std::cout << g_count << std::endl;
    return 0;
}

boost简化操作创建一组线程的操作:前置准备

#include<stdio.h>
#include <mutex>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
std::mutex g_mutex;
int g_count = 0;


void counter(){
     


    for(int i = 0; i < 100000; i++){
     
        g_mutex.lock();
        g_count++;
        // 前面代码如有异常,unlock 就调不到了。
        g_mutex.unlock();
    }
}

int main()
{
     
    boost::thread_group threadGroup;
    for(int i = 0; i < 4; i++){
     
        threadGroup.create_thread(&counter);
    }

    threadGroup.join_all();
    std::cout << g_count << std::endl;
    return 0;
}

  1. lock()是阻塞式的,如果没有获取到锁,就会一直阻塞到这一句。直到当前锁被释放,然后才会唤醒当前线程,尝试区获取锁。
#include
#include 
#include 
#include 
std::mutex mutex;
volatile int counter = 0;
void run() {
     
    std::cout << "wait for mutex.lock 【死等】\n" << std::endl;
    mutex.lock();
     std::cout << "成功获取到了锁 【死等】\n" << std::endl;
    ++counter;
    mutex.unlock();
}

int main()
{
     

    mutex.lock();
    std::thread varname(run);
    varname.join();
    mutex.unlock();
    std::cout << "counter should 100000, but counter = " << counter << "\n";

    return 0;
}

try_lock:

  • 如果互斥锁当前未被任何线程锁定,则调用线程将其锁定(从此点开始,直到调用其成员解锁,该线程拥有互斥锁)。
  • .如果互斥锁当前被另一个线程锁定,则该函数将失败并返回false,而不会阻塞(调用线程继续执行)。
  • 如果互斥锁当前被调用此函数的同一线程锁定,则会产生死锁(具有未定义的行为)。 请参阅recursive_mutex以获取允许来自同一线程的多个锁的互斥锁类型。
#include<stdio.h>
#include <mutex>
#include <iostream>
#include <thread>

volatile int counter = 0; // volatile 避免编译器优化  每次都去内存里去读
void run(std::mutex &mutex) {
     
    for (int i = 0; i<10000; ++i)
    {
     
        if (mutex.try_lock())
        {
     
            ++counter;
            mutex.unlock();
        }
    }
}

int main()
{
     
    std::mutex mutex;

    std::thread threads[10];
    for (int i = 0; i<10; ++i){
     
        threads[i] = std::thread(run, std::ref(mutex));
    }
    for (auto& th : threads){
     
        th.join();
    }

    std::cout << "counter should 100000, but counter = " << counter << "\n"; 

    return 0;
}

// 由于try_lock没有获取到锁就会直接返回一个false,从而导致有一个counter没有自增,而我们不知道什么时候会失败,所以结果是不可预期的。这是线程不安全的

std::time_mutex

std::time_mutexstd::mutex 多了两个成员函数,try_lock_for(),try_lock_until()

  • try_lock_for
    • 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock() 如果被调用时没有获得锁则直接返回 false
    • 如果在此期间其他线程释放了锁,则该线程可以获得对斥量的锁
    • 如果超时(即在指定时间内还是没有获得锁),则返回 false。
  • try_lock_until::
    • 函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,
    • 如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,
    • 如果超时(即在指定时间内还是没有获得锁),则返回 false。
/* 作用:在参数时间内阻塞尝试获取锁
 * 参数:一个 chrono::duration 对象,指定此方法尝试获取 mutex 所有权的最大时间量。
 * 返回值:如果此方法成功获取 mutex 的所有权,则为 true;否则为 false。
 * 备注:如果调用线程已拥有 mutex,则该行为不确定
*/
template<class Rep, class Period>
   bool try_lock_for(const chrono::duration<Rep, Period>& Rel_time);

/* 作用:在参数时间到达之前阻塞尝试获取锁
 * 参数:一个时间点,指定阈值,在此之后此方法不再尝试获取 mutex 所有权。
 * 返回值:如果此方法成功获取 mutex 的所有权,则为 true;否则为 false。
 * 备注:如果调用线程已拥有 mutex,则该行为不确定
*/
template<class Clock, class Duration>
   bool try_lock_for(const chrono::time_point<Clock, Duration>& Abs_time);
bool try_lock_until(const xtime *Abs_time);

canjian

try_lock_for【为什么没有阻塞???】

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

void run500ms(std::timed_mutex &mutex) {
     
    std::cout << "wait for mutex.lock [阻塞,直到超时/获取到锁])\n" << std::endl;
    auto _500ms = std::chrono::minutes (10);
    if (mutex.try_lock_for(_500ms)) {
     
        std::cout << "获得了锁" << std::endl;
    } else {
     
        std::cout << "超时, 未获得锁\n" << std::endl;
    }
}
int main() {
     
    std::timed_mutex mutex;

    mutex.lock();
    std::thread thread(run500ms, std::ref(mutex));
    thread.join();
    mutex.unlock();

   // getchar();
    return 0;
}
#include 
#include 
#include 
#include 
#include 

std::mutex cout_mutex; // control access to std::cout
std::timed_mutex mutex;

void job(int id)
{
     

    using Ms = std::chrono::minutes ;
    std::ostringstream stream;
    for (int i = 0; i < 3; ++i) {
     
        if (mutex.try_lock_for(Ms(100))) {
     
            stream << "success ";
            std::this_thread::sleep_for(Ms(100));
            mutex.unlock();
        } else {
     
            stream << "failed ";
        }
       // std::this_thread::sleep_for(Ms(100));
    }

    std::lock_guard<std::mutex> lock(cout_mutex);
    std::cout << "[" << id << "] " << stream.str() << "\n";
}

int main()
{
     
    std::vector<std::thread> threads;
    threads.reserve(4);
    for (int i = 0; i < 4; ++i) {
     
        threads.emplace_back(job, i);
    }

    for (auto& i: threads) {
     
        i.join();
    }
}

try_lock_until【为什么没有阻塞???】

#include 
#include 
#include 
#include 

void run500ms(std::timed_mutex &mutex) {
     
    std::cout << "wait for mutex.lock [阻塞,直到超时/获取到锁])\n" << std::endl;
    auto _500ms = std::chrono::minutes (10);
    if (mutex.try_lock_until(std::chrono::system_clock::now() + _500ms)) {
     
        std::cout << "获得了锁" << std::endl;
    } else {
     
        std::cout << "超时, 未获得锁\n" << std::endl;
    }
}
int main() {
     
    std::timed_mutex mutex;

    mutex.lock();
    std::thread thread(run500ms, std::ref(mutex));
    thread.join();
    mutex.unlock();

   // getchar();
    return 0;
}

std::lock_guard

  • 根据对象的析构函数自动调用的原理,c++11推出了std::lock_guard自动释放锁,其原理是:声明一个局部的lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁。最终的结果就是:在定义该局部对象的时候加锁(调用构造函数),出了该对象作用域的时候解锁(调用析构函数)。特点如下:
    • 创建即加锁,作用域结束自动析构并解锁,无需手工解锁
    • 不能中途解锁,必须等作用域结束才解锁
    • 不能复制
  • lock_guard 不会产生死锁,但是管理不了Mutex的生命周期
#include
#include 
#include 
#include 

volatile int counter = 0; // volatile 避免编译器优化  每次都去内存里去读
void run(std::mutex &mutex) {
     
    for (int i = 0; i<10000; ++i)
    {
     
        std::lock_guard<std::mutex> lock(mutex);  // 加锁
        ++counter;
    }  // 离开当前作用域,自动解锁
}

int main()
{
     
    std::mutex mutex;

    std::thread threads[10];
    for (int i = 0; i<10; ++i){
     
        threads[i] = std::thread(run, std::ref(mutex));
    }
    for (auto& th : threads){
     
        th.join();
    }

    std::cout << "counter should 100000, but counter = " << counter << "\n";

    return 0;
}
#include
#include 
#include 
#include 
boost::mutex mutex;
volatile int counter = 0; // volatile 避免编译器优化  每次都去内存里去读
void run() {
     
    for (int i = 0; i<10000; ++i)
    {
     
        boost::lock_guard<boost::mutex> lock(mutex);
        ++counter;
    }
}

int main()
{
     

    boost::thread_group threads;
    for (int i = 0; i < 10; ++i) {
     
        threads.create_thread(&run);
    }

    threads.join_all();

    std::cout << "counter should 100000, but counter = " << counter << "\n";

    return 0;
}

std::unique_lock

结合std::condition_variable使用


#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <deque>
#include <condition_variable>

std::deque<int> global_deque;
std::mutex global_mutex;
std::condition_variable global_ConditionVar;

// 生产者线程
void Producer()
{
     
    while (true)
    {
     
        // 休眠5毫秒
        std::this_thread::sleep_for(std::chrono::milliseconds(5));

        std::unique_lock<std::mutex> lock(global_mutex);
        global_deque.push_front(1);
        std::cout << "生产者生产了数据" << std::endl;
        global_ConditionVar.notify_all();
    }
}

// 消费者线程
void Consumer()
{
     
    while (true)
    {
     
        std::unique_lock<std::mutex> lock(global_mutex);

        // 当队列为空时返回false,则一直阻塞在这一行
        global_ConditionVar.wait(lock, [] {
     return !global_deque.empty(); });
        global_deque.pop_back();

        std::cout << "消费者消费了数据" << std::endl;
        global_ConditionVar.notify_all();
    }
}

int main()
{
     

    std::thread consumer_Thread(Consumer);
    //std::thread consumer_Thread1(Consumer);

    std::thread producer_Thread(Producer);

    consumer_Thread.join();
    //consumer_Thread1.join();
    producer_Thread.join();


    getchar();
    return 0;
}

C++ 11互斥锁的应用(针对于多线程的情况)
C++ 多线程 (4) 互斥量(mutex)与锁(lock)
C++11多线程——lock详解

你可能感兴趣的:(#,C++)