C++多线程学习(三):锁资源管理和条件变量

参考引用

  • C++11 14 17 20 多线程从原理到线程池实战
  • 代码运行环境:Visual Studio 2019

1. 利用栈特性自动释放锁 RAII

1.1 什么是 RAII

  • RAII (Resource Acquisition Is Initialization):使用局部对象来管理资源的技术称为资源获取即初始化
    • 它的生命周期是由操作系统来管理,无需人工介入
    • 资源的销毁容易忘记,造成死锁或内存泄漏

1.2 手动实现 RAII 管理 mutex 资源

  • thread_RAII.cpp

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    // RAII
    class XMutex {
    public:
        // 在构造函数中锁住,一生成对象 mux 就拿到锁
        XMutex(mutex &mux) : mux_(mux) {
            cout << "Lock" << endl;
            mux.lock();
        }
        // 析构函数中释放
        ~XMutex() {
            cout << "Unlock" << endl;
            mux_.unlock();
        }
    
    private:
        // 引用方式存储锁,引用必须在初始化时就要赋值
        mutex& mux_;
    };
    
    static mutex mux;
    
    void TextMutex(int status) {
        XMutex lock(mux);  // 不需要关心锁的 unlock() 释放
    
        if (status == 1) {
            cout << " = 1" << endl;
            return;
        } else {
            cout << " != 1" << endl;
            return;
        }
    }  // 超出这个大括号后,会调用析构函数释放栈中资源
    
    int main(int argc, char* argv[]) {
        TextMutex(1);
        TextMutex(2);
    
        getchar();
    
        return 0;
    }
    
  • 控制台输出

    Lock
     = 1
    Unlock
    Lock
     != 1
    Unlock
    

2. lock_guard:C++11 支持的 RAII 管理互斥资源

  • C++11 实现严格基于作用域的互斥体所有权包装器

  • adopt_lock:C++11 类型为 adopt_lock_t,假设调用方已拥有互斥的所有权

  • 通过 {} 控制锁的临界区(栈区间),出了 {} 后自动释放锁资源

  • thread_RAII2.cpp

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    static mutex gmutex;
    
    void TestLockGuard(int i) {
        gmutex.lock();
        {
            // 已经拥有锁,不锁定,退出解锁
            lock_guard<mutex> lock(gmutex, adopt_lock);
            // 结束释放锁
        }
        {
            lock_guard<mutex> lock(gmutex);
            cout << "begin thread " << i << endl;
        }
        for (;;) {
            {
                lock_guard<mutex> lock(gmutex);
                cout << "In " << i << endl;
            }
            this_thread::sleep_for(500ms);
        }
    }
    int main(int argc, char* argv[]) {
        for (int i = 0; i < 3; i++) {
            thread th(TestLockGuard, i + 1);
            th.detach();
        }
        getchar();
    
        return 0;
    }
    
  • 控制台输出

    begin thread 1
    In 1
    begin thread 3
    In 3
    begin thread 2
    In 2
    In 1
    In 2
    In 3
    In 1
    ...
    

3. unique_lock:C++11 实现可移动的互斥体所有权包装器

  • 支持临时释放锁 unlock

  • 支持 adopt_lock:(已经拥有锁,不加锁,出栈区会释放)

  • 支持 defer_lock:(延后拥有,不加锁,出栈区不释放)

  • 支持 try_to_lock:尝试获得互斥的所有权而不阻塞,获取失败退出栈区不会释放,通过 owns_lock() 函数判断

  • thread_RAII3.cpp

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main(int argc, char* argv[]) {
        {
            static mutex mux;
            {
                unique_lock<mutex> lock(mux);
                lock.unlock();  // 临时释放锁
                
                lock.lock();
            }
            {
                // 已经拥有锁 不锁定,退出栈区解锁
                mux.lock();
                unique_lock<mutex> lock(mux, adopt_lock);
            }
            {
                // 延后加锁 不拥有 退出栈区不解锁
                unique_lock<mutex> lock(mux, defer_lock);
                // 加锁 退出栈区解锁
                lock.lock();
            }
            {
                //mux.lock();
                // 尝试加锁 不阻塞 失败不拥有锁
                unique_lock<mutex> lock(mux, try_to_lock);
    
                if (lock.owns_lock()) {
                    cout << "owns_lock" << endl;
                } else {
                    cout << "not owns_lock" << endl;
                }
            }
        }
    
        getchar();
    
        return 0;
    }
    

4. shared_lock C++14 实现可移动的共享互斥体所有权封装器

int main(int argc, char* argv[]) {
    {
        // 共享锁
        static shared_timed_mutex tmux;
    
        // 读取锁--共享锁
        {
            // 调用共享锁 
            shared_lock<shared_timed_mutex> lock(tmux);
            cout << "read data" << endl;
            // 退出栈区 释放共享锁
        }
    
        // 写入锁--互斥锁
        {
            unique_lock<shared_timed_mutex> lock(tmux);
            cout << "write data" << endl;
        }
    }
}

5. 案例:使用互斥锁和 List 实现多线程通信

  • 封装线程基类 XThread 控制线程启动和停止
  • 模拟消息服务器线程,接收字符串消息,并模拟处理
  • 通过 unique_lock 和 mutex 互斥访问 list 消息队列
  • 主线程定时发送消息给子线程

5.1 xthread.h

#pragma once
#include 

class XThread {
public:
    // 启动线程
    virtual void Start();

    // 设置线程退出标志 并等待
    virtual void Stop();

    // 等待线程退出(阻塞)
    virtual void Wait();

    // 线程是否退出
    bool is_exit();

private:
    // 线程入口
    virtual void Main() = 0;
    bool is_exit_ = false;
    std::thread th_;
};

5.2 xthread.cpp

#include "xthread.h"

using namespace std;

// 启动线程
void XThread::Start() {
    is_exit_ = false;
    th_ = thread(&XThread::Main, this);
}

// 设置线程退出标志 并等待
void XThread::Stop() {
    is_exit_ = true;
    Wait();
}

// 等待线程退出(阻塞)
void XThread::Wait() {
    if (th_.joinable())
        th_.join();
}

// 线程是否退出
bool XThread::is_exit() {
    return is_exit_;
}

5.3 xmsg_server.h

#pragma once
#include "xthread.h"
#include 
#include 
#include 

class XMsgServer : public XThread {
public:
    // 给当前线程发消息
    void SendMsg(std::string msg);

private:
    // 处理消息的线程入口函数
    void Main() override;

    // 消息队列缓冲
    std::list<std::string> msgs_;

    // 互斥访问消息队列
    std::mutex mux_;
};

5.4 xmsg_server.cpp

#include "xmsg_server.h"
#include 

using namespace std;
using namespace this_thread;

// 处理消息的线程入口函数
void XMsgServer::Main() {
    while (!is_exit()) {
        sleep_for(10ms);
        unique_lock<mutex> lock(mux_);
        if (msgs_.empty())
            continue;
        while (!msgs_.empty()) {
            // 消息处理业务逻辑
            cout << "recv : " << msgs_.front() << endl;
            msgs_.pop_front();
        }

    }
}

// 给当前线程发消息
void XMsgServer::SendMsg(std::string msg) {
    unique_lock<mutex> lock(mux_);
    msgs_.push_back(msg);
}

5.5 thread_msg_server

#include "xmsg_server.h"
#include 

using namespace std;

int main(int argc, char* argv[]) {
    XMsgServer server;
    server.Start();
    for (int i = 0; i < 10; i++) {
        stringstream ss;
        ss << " msg : " << i + 1;
        server.SendMsg(ss.str());
        this_thread::sleep_for(500ms);
    }
    server.Stop();

    return 0;
}

6. 条件变量应用场景_生产者消费者信号处理步骤

  • 生产者-消费者模型
    • 生产者和消费者共享资源变量(List 队列)
    • 生产者生产一个产品,通知消费者消费
    • 消费者阻塞等待信号:获取信号后消费产品(取出 List 队列中的数据)

6.1 改变共享变量的线程步骤

  • 准备好信号量
    std::condition_variable cv;
    
  • 1、获得 std::mutex(常通过 std::unique_lock)
    unique_lock lock(mux);
    
  • 2、在获取锁时进行修改
    msgs_.push_back(data);
    
  • 3、释放锁并通知读取线程
    lock.unlock();
    cv.notify_one();  // 通知一个等待信号线程
    cv.notify_all();  // 通知所有等待信号线程
    

6.2 等待信号读取共享变量的线程步骤

  • 1、获得与改变共享变量线程共同的 mutex
    unique_lock lock(mux);
    
  • 2、wait() 等待信号通知
    // 解锁,并阻塞等待 notify_one notify_all 通知
    cv.wait(lock);
    
    // 接收到通知会再次获取锁标志,也就是说如果此时 mux 资源被占用,wait 函数会阻塞
    msgs_.front();
    
    // 处理数据
    msgs_pop_front();
    

7. condition_variable 读写线程同步

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

list<string> msgs_;
mutex mux;
condition_variable cv;

void ThreadWrite() {
    for (int i = 0;;i++) {
        stringstream ss;
        ss << "Write msg " << i;
        unique_lock<mutex> lock(mux);
        msgs_.push_back(ss.str());
        lock.unlock();
        cv.notify_one();  // 发送信号
        //cv.notify_all();
        this_thread::sleep_for(3s);
    }
}
void ThreadRead(int i) {
    for (;;) {
        cout << "read msg" << endl;
        unique_lock<mutex> lock(mux);
        //cv.wait(lock);  // 解锁、阻塞等待信号
        cv.wait(lock, [i] 
            {
                cout << i << " wait" << endl;
                return !msgs_.empty(); 
            });
        // 获取信号后锁定
        while (!msgs_.empty()) {
            cout << i << " read " << msgs_.front() << endl;
            msgs_.pop_front();
        }

    }
}
int main(int argc, char* argv[]) {
    thread th(ThreadWrite);
    th.detach();
    for (int i = 0; i < 3; i++)  {
        thread th(ThreadRead, i + 1);
        th.detach();
    }
    getchar();

    return 0;
}

你可能感兴趣的:(C++多线程学习,c++,学习,RAII,lock_guard,unique_lock,shared_lock,互斥锁)