• C++线程互斥、同步


     一、线程互斥
    如果多个线程需要访问且可能修改同一个变量,那么需要加锁,保证同一时刻只有一个线程可以访问,这个动作即最小“原子操作”
    方式1:
    使用c++提供的类mutex,lock,unlock即可保证线程同步
    #include <mutex>
    mutex mx;
    int haha()
    { mx.
    lock(); do()   { // } mx.unlock(); return 0; }

    缺点:如果do函数异常,这里会造成死锁

     方式2:使用lock_guard代替mutex

    为了解决上述问题,使用lock_guard,这个模板类在构造函数里lock mutex,在析构函数里unlock mutex,所以不用自己在unlock

    #include <mutex>
    mutex mx;
    int haha()
    {
       {//使用作用域,只需要锁住改变公共变量的代码
          std::lock_guard<std::mutex> lck(m_mutex);
          do()
         {
               //
          }
       } 
       return 0;
    }

    方式3:使用C++11原子操作

    #include <atomic>
    #include <thread>
    #include <list>
    std::atomic_int g_iCount = 100;
    
    void threadfunc1()
    {
        while (g_iCount > 0)
        {
            printf("threadfunc1 g_iCount:%d
    ", --g_iCount);
        }
    }
    
    void threadfunc2()
    {
        while (g_iCount > 0)
        {
            printf("threadfunc2 g_iCount:%d
    ", --g_iCount);
        }
    }
    
    int main(int argc, char** argv)
    {
        thread t1(threadfunc1);
        thread t2(threadfunc2);
    
        t1.join();
        t2.join();
    
        return 0;
    }
    atomic_int 类型就是C++11提供的原子类型,还有很多其他类似的如bool、long、char等等,可以更高效的提供多线程安全访问,比线程锁更高效,因为它是汇编级别的。不过只有基本数据类型,其他数据结构和自定义类型就不行了

    二、线程同步

    线程同步是指不同线程按指定顺序做某事情,某一线程是否做某动作,是由另一个线程来管理的,如线程池按顺序调度队列,消费者会等待生产者生产了任务后再进行工作。如果不同步,我们一般会使用死循环来访问。这里举例生产-消费模型

    生产者每隔t1时间会生产出一个任务,消费者选择最优方式去调度任务

    方式1、不同步,使用死循环

    std::deque<int> q;
    std::mutex mu;
    
    void function_1() 
    {
        int count = 10;
        while (count > 0) 
        {
            std::unique_lock<std::mutex> locker(mu);
            q.push_front(count);
            locker.unlock();
            std::this_thread::sleep_for(std::chrono::seconds(1));
            count--;
        }
    }
    
    void function_2() 
    {
        int data = 0;
        while (data != 1) 
        {
            std::unique_lock<std::mutex> locker(mu);
            if (!q.empty()) 
            {
                data = q.back();
                q.pop_back();
                locker.unlock();
                std::cout << "t2 got a value from t1: " << data << std::endl;
            }
            else 
         { locker.unlock(); } } }
    int main() { std::thread t1(function_1); std::thread t2(function_2); t1.join(); t2.join(); return 0; }
    【代码来源:https://www.jianshu.com/p/c1dfa1d40f53】

    这里消费者会死循环判断队列是否有数据,如果没有数据,也会死循环的跑,就会非常占cpu,此线程几乎会占满一个内核【单核计算机可能达到100%】,这种方式肯定不好。

    有没有这样一种功能呢?如果队列没有任务,消费者就不占用cpu,释放内核,当生产者生产了任务后,唤醒消费者,消费者从队列里取数据,当任务做完,队列里没有任务后,继续释放内核...

    看方式2:

    C++11提供了条件变量,std::condition_variable

    需要和互斥锁unique_lock一起使用。

    继续看代码【代码来源:https://www.jianshu.com/p/c1dfa1d40f53】

    std::deque<int> q;
    std::mutex mu;
    std::condition_variable cond;
    
    void function_1() 
    {
        int count = 10;
        while (count > 0) 
        {
            std::unique_lock<std::mutex> locker(mu);
            q.push_front(count);
            locker.unlock();
            cond.notify_one();  // Notify one waiting thread, if there is one.
            std::this_thread::sleep_for(std::chrono::seconds(1));
            count--;
        }
    }
    
    void function_2() 
    {
        int data = 0;
        while (data != 1) 
        {
            std::unique_lock<std::mutex> locker(mu);
            while (q.empty())
                cond.wait(locker); // Unlock mu and wait to be notified
            data = q.back();
            q.pop_back();
            locker.unlock();
            std::cout << "t2 got a value from t1: " << data << std::endl;
        }
    }
    int main() 
    {
        std::thread t1(function_1);
        std::thread t2(function_2);
        t1.join();
        t2.join();
        return 0;
    }

    核心:

    ①、在消费者里判断队列是否为空后,如果不为空则wait,等待生产者发送notify信号

    ②、在生产者那里,如果生产了任务,则发送notify信号,告诉消费者可以试图退出wait,判断队列是否为空,如果有任务则调度处理任务,如果还是空则说明此次notify是错误的,可能是其他地方发出来干扰的,生产者继续wait。

  • 相关阅读:
    p(str or array) 传递数据以易于阅读的样式格式化后输出 bootstarp样式的打印函数
    [Err] 1067
    php 正则表达式
    Docker使用及dnmp构建
    记一次Ubuntu18.04升级到19.10的经历
    面试-Redis
    ubuntu截图软件deepin scrot
    docker 搭建 Hadoop
    Docker 遇到的坑
    RabbitMQ遇到的坑
  • 原文地址:https://www.cnblogs.com/judes/p/11132918.html
Copyright © 2020-2023  润新知