• c++11多线程库


    标准线程库,c++11引入,包含原子操作库、互斥锁、条件变量。。。

    一、线程库<thread>

    创建线程的四种方法:

    1. 通过全局函数创建线程

    线程类的构造函数是变参构造函数,第一个参数是线程函数,后面的参数为线程函数的参数(参数通过值传递方式,若要引用传递须加std::ref())。

    thread   t1 (counter, 1, 6);   //void counter(int, int);

    2. 通过函数对象创建线程

    //class Counter 实现 operator()

    1) thread   t1{Counter(1, 20)};   //c++统一推荐方法

    2) Counter    c(1, 20);

       thread   t2(c);

    3) thread   t3(Counter(1,20));

    比较第一种和第三种构造方式,如果函数对象的构造函数不需要任何参数。 thread  t3(Counter());是不行的,因为编译器会认为你在声明一个函数,函数名为t3,此时只能用第一种构造方式。

    3. 通过lambda表达式创建线程

    thread   t1 ([](int, int){/*函数体*/}, 1, 6); 

    4. 通过成员函数创建线程

    // class Counter  c();

    thread   t{&Counter::process, &c};

    一般常见的是一个类自己创建一个后台处理线程:thread   t{&Counter::process, this};

    线程本地存储 thread_local

    thread_local    int   n;

    n作为线程参数传递给线程,那么每个线程有一个n的副本,在线程整个生命周期中存在,且只初始化一次,如同static局部变量。

    二、原子操作库<atomic>

    多线程编程经常需要操作共享的内存,在读/写过程中会导致竞争条件。

    例如:

    int   counter = 0;

    ............

    ++counter;  //因为++counter不时原子操作,多个线程中出现此操作时不是线程安全的。

    应该用:

    atomic<int>   counter(0);  //等效于 atomic_int   counter(0);

    ............

    ++counter;  //此时多个线程执行++counter是一个原子操作,是线程安全的。

    例:
    
    void   func( std::atomic<int>&   counter)
    {
      for( int   i=0;   i<1000;   ++i )
        ++counter;
    }
    
    int   main()
    {
      std::atomic<int>   counter(0);
      std::vector<std::thread>   threads;
    
      for( int i=0;   i<10;   ++i )
        //线程参数总是值传递,若要传递引用,须加std::ref()。(头文件<functional>中)
        threads.push_back( std::thread{ func, std::ref(counter)} );
    
      for( auto&   t  :  threads )
        t.join();    //调用join,如果线程未结束,则main函数阻塞于此。
    
      std::count<<"Result="<<counter<<std::endl;
    
      return 0;
    }
    
    /*join的调用会导致调用线程阻塞于此,若不希望调用线程阻塞,但又想知道被调线程是否结束,应当用其它方式,例如消息...*/

    三、互斥 <mutex>

    编写多线程必须分外留意操作顺序,如果无法避免线程共享数据,则必须提供同步机制,保证一次只有一个线程能更改数据。使用互斥解决竞争条件,可能导致死锁。

    1. 互斥体类

    1) 非定时互斥体类   std::mutex         std::recursive_mutex

      lock() : 尝试获取锁,并且阻塞直到获取锁。

      try_lock() : 尝试获取锁,并立即返回,成功获取返回true,否则false。

      unlock() : 释放锁。

    mutex与recursive_mutex的区别在于,前者已经获得所后不得再尝试获取,这会死锁,后者能递归获取,注意释放次数应与获取次数相等。

    2) 定时互斥锁类   std::timed_mutex     std::recursive_timed_mutex

      lock() ,      try_lock() ,        unlock()

      try_lock_for(rel_time) : 指定相对时间内获得返回true, 超时返回false。

      try_lock_until(abs_time) : 指定系统绝对时间内获得返回true, 超时返回false。

    timed_mutex与recursive_timed_mutex区别同上。

    2. 锁类

    锁类是一个包装器,析构函数会自动释放关联的互斥体。

    1) 简单锁   std::lock_guard

      其构造函数会要求获得互斥体,并阻塞直到获得锁。

    2) 复杂锁   std::unique_lock 

         explict   unique_lock( mutex_type&   m); //阻塞直到获得锁。
    
      unique_lock(mutex_type&   m,   defer_lock_t)  noexcept; //保存一个互斥体引用,不会立即尝试获得锁。锁可以在以后获得。
    
      unique_lock(mutex_type&   m,   try_to_lock_t); //尝试获得引用的互斥锁,未能获得也不阻塞。
    
      unique_lock(mutex_type&   m,   adopt_lock_t); //该锁假定线程获得引用的互斥锁,并负责管理这个锁。
    
      template<class Clock,   class Duration>
      unique_lock(mutex&  m,  const  chrono::time_point<Clock, Duration>&  abs_time); //尝试获取该锁,直到超过给定的绝对时间。
    
      template<class Rep,   class Period>
      unique_lock(mutex&  m,  const  chrono::duration<Rep, Period>&  rel_time); //尝试获取该锁,直到超过给定的相对时间。

    unique_lock类还支持lock(), try_lock(), try_lock_for(), try_lock_until()等方法。

    通过owns_lock()查看是否获得了这个锁;也可以用if对unique_lock对象直接判断是否获得锁,因为它定义了bool()运算符。

    3. 获得多个互斥体对象上的锁

    1) 泛型lock可变参数模板函数

      template <class L1,  class L2,  class...L3>

      void  lock(L1&,  L2&,  L3&...);

    按顺序锁定,如果一个互斥体抛出异常,会对已获得的锁unlock。

    2) 泛型try_lock

      template <class L1,  class L2,  class...L3>

      int  try_lock(L1&,  L2&,  L3&...);

    通过顺序调用互斥体对象的try_lock,成功返回-1,失败返回从0开始的位置索引,并对已获得的锁unlock。

    参数顺序每次应保持一致, 否则易死锁。

    4. std::call_once         std::once_flag

    保证call_once调度的函数只被执行一次。

    5. 实例:

    // 1. 简单锁
    mutex   mMutex;
    lock_guard<mutex> mLock(mMutex);
    
    // 2. 定时锁
    timed_mutex   mTimeMutex;
    unique_lock<timed_mutex> mLock(mTimedMutex, chrono::milliseconds(200));
    
    // 3. 泛型
    mutex   mut1;
    mutex   mut2;
    unique_lock<mutex>  lock1(mut1, defer_lock_t());
    unique_lock<mutex>  lock2(mut2, defer_lock_t());
    lock(lock1, lock2);
    
    // 4. 双重检查锁定算法 (代替call_once的用法)
    class MyClass
    {
    public:
         void init() { p = new int(0);   cout<<"Init"<<endl;}
    private:
         int *p;
    }
    
    MyClass  var;
    bool  initialized = false;
    mutex   mut;
    void  func() 
    {
         if( ! initialized)               //一次检查
         {
               unique_lock<mutex>  lock1(mut);
               if( ! initialized)         //两次检查
               {
                     var.init();
                     initialized  = true;
               }
         }
         cout<<"OK"<<endl;
    }
    //两次检查initialized。获得锁之前和获得锁之后,确保init只调用一次。

    四、条件变量 <condition_variable>

    1. std::condition_variable  只能等待unique_lock<mutex>的条件变量

     notify_one();  //唤醒等待这个条件变量的线程之一
     notify_all();    //唤醒所有等待这个条件变量的线程
    
     // 1)前提是已经获得lk的锁
     // 2)调用wait会unlock  lk,然后等待
     // 3)当被唤醒后 lock  lk
     wait( unique_lock<mutex>& lk);  
    
     wait_for(unique_lock<mutex>& lk, const chrono::duration<Rep,Period>& rel_time);
    
     wait_until(unique_lock<mutex>&lk, const chrono::time_point<Clock,Duration>& abs_time);

    2. std::condition_variable_any      支持任何类型的Lock类

    3.

    //例:向队列中加入数据,当队列不为空时,后台线程被唤醒处理数据
    std::queue<std::string> mQueue;
    std::mutex                   mMutex;
    std::condition_variable   mCondVar;
    
    
    //向队列加入数据的线程
    unique_lock<mutex>  lock(mMutex);
    mQueue.push( data);
    mCondVar.notify_all();
    
    
    //后台处理数据的线程
    unique_lock<mutex>  lock(mMutex);
    while(true)
    {
         //1.先释放lock  2.然后等待被唤醒  3.被唤醒后等待获取lock
         mCondVar.wait(lock);
    
         // process...
    }

    五、 future

    promise/future模型方便获取线程返回的结果、线程间通信、处理异常

  • 相关阅读:
    .NET ORM 的 “SOD蜜”--零基础入门篇
    EF+MySQL乐观锁控制电商并发下单扣减库存,在高并发下的问题
    PDF.NET SOD 开源框架红包派送活动 && 新手快速入门指引
    DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架
    64位系统使用Access 数据库文件的彻底解决方法
    DDD为何叫好不叫座?兼论DCI与业务分析的方法论
    买的永远没有卖的精:评北京联通宽带送电视送手机优惠促销活动
    在数据库上实现类似铁路售票锁票功能
    .NET DLR 上的IronScheme 语言互操作&&IronScheme控制台输入中文的问题
    U深度利用iso文件制作U盘启动盘
  • 原文地址:https://www.cnblogs.com/songcf/p/3136034.html
Copyright © 2020-2023  润新知