c++11 多线程 -- 基本使用

2015-07-20 17:06:26 ? 作者: ? 浏览: 3

c++11 多线程 – 基本使用

前言:这篇文章仅针对没有使用过c++11线程库的童鞋来快速入门,也是自己的一个简单记录,内容比较基础。

-1.线程的基本使用
-2.互斥量
-3.条件变量
-4.原子变量


1.线程的基本使用

代码:

#include 
   
     #include 
    
      #include 
     
       #include 
      
        #include 
       
         int k = 0; void fun(void) { //线程休眠,chrono是c++11的时间相关库。 std::this_thread::sleep_for(std::chrono::seconds(3)); for(int i = 0; i < 10; ++i) { std::cout << hello world << std::endl; k++; } } int main(int argc, char *argv[]) { //创建线程对象 std::thread t1(fun); //输出线程id和cpu核数 std::cout << ID: << t1.get_id() << std::endl; std::cout << CPU: << std::thread::hardware_concurrency() << std::endl; //主函数阻塞等待线程结束 t1.join(); //主函数和线程函数分离执行,线程变为后台线程 //t1.detach(); std::cout << k << std::endl; return EXIT_SUCCESS; }
       
      
     
    
   

注意:
1.linux下用gcc或clang必须加-pthread连接到线程库,否则会出错。
2.主线程函数不能提前结束于新创建的线程函数,因为在c++11中,线程也是对象,主函数结束线程对象即销毁。
3.t.join()是主函数阻塞等待线程结束才能结束,主函数会继续执行,并阻塞在return处
t.detach()主函数和线程函数分离,各自执行各自的,线程变为后台线程。
4.可通过bind和lambda创建线程
可以将线程保存在容器中,以保证线程对象的声明周期。
但是注意线程没有拷贝构造函数,有移动构造函数。
这里写图片描述
图上可以看出拷贝构造函数为delete。


2.互斥量

分为4种
std::mutex 独占的互斥量,不能递归使用
std::timed_mutex 带超时的独占的互斥量,不能递归使用
std::recursive_mutex 递归互斥量,不带超时功能
std::recursive_timed_mutex 带超时的递归互斥量

代码:

#include 
   
     #include 
    
      #include 
     
       #include 
      
        #include 
       
         #include 
        
          std::mutex g_lock; int i = 0; void func(void) { //使用RAII手法,在离开作用域时自动释放 std::lock_guard
         
          locker(g_lock); //正常的互斥锁上锁 //g_lock.lock(); i++; std::cout << i << std::endl; //互斥锁解锁 //g_lock.unlock(); } int main(int argc, char *argv[]) { std::thread t1(func); std::thread t2(func); std::thread t3(func); t1.join(); t2.join(); t3.join(); return EXIT_SUCCESS; }
         
        
       
      
     
    
   

注意:
1.多次获取互斥量可能会发生死锁,所以我们调用std::recursive_mutex递归锁,允许同一线程多次获得该锁,一般不要使用递归锁,原因:<1.用到递归锁会使得程序的逻辑变复杂,使用到递归锁的程序一般可以简化。<2.递归锁比非递归锁效率低。<3.递归锁的可重入次数是有限的,超过也会报错。
2.可以使用带超时时间的互斥锁,避免阻塞在等待互斥锁上。
3.unique_lock: 是一个通用的互斥量封装类。与lock_guard不同,它还支持延迟加锁、时间锁、递归锁、锁所有权的转移并且还支持使用条件变量。这也是一个不可复制的类,但它是可以移动的类。


3.条件变量

阻塞一个或多个线程,直到收到另外一个线程发来的通知或者超时,才会唤醒当前阻塞的进程
条件变量需要和互斥量配合使用
c++11提供了两种条件变量
1.std::condition_variable,配合std::unique_lock进行wait操作
2.std::condition_variable_any,和任意带有lock,unlock的mutex进行搭配使用,比较灵活但效率略低。
条件变量的wait还有一个重载的方法,可以设置一个条件,条件变量会先检查判断式是否满足条件。

原理:
当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。

代码:用c++11多线程实现同步队列

#include 
   
     #include 
    
      #include 
     
       #include 
      
        #include 
       
         #include 
        
          #include 
         
           #include 
          
            #include 
           
             #include 
            
              #include 
             
               template
              
                class SynQueue { public: //构造函数 SynQueue(int MaxSize): m_maxsize(MaxSize) { } //将T类型对象放入队列 void Put(const T&x) { std::lock_guard
               
                locker(m_mutex); while(isFull()) { //如果满了,等待 m_notFull.wait(m_mutex); } m_queue.push_back(x); //通过条件变量唤醒一个线程,也可以所有线程 m_notEmpty.notify_one(); } //将T类型对象从队列取出 void Take(T&x) { std::lock_guard
                
                  locker(m_mutex); while(isEmpty()) { std::cout << no resource... please wait << std::endl; m_notEmpty.wait(m_mutex); } x = m_queue.front(); m_queue.pop_front(); m_notFull.notify_one(); } //判断队列是否为空 bool Empty() { std::lock_guard
                 
                   locker(m_mutex); return m_queue.empty(); } //判断队列是否为满 bool Full() { std::lock_guard
                  
                    locker(m_mutex); return m_queue.size() == m_maxsize; } //返回队列大小 size_t Size() { std::lock_guard
                   
                     locker(m_mutex); return m_queue.size(); } private: //判断空或满,内部使用不需要加锁 bool isFull() const { return m_queue.size() == m_maxsize; } bool isEmpty() const { return m_queue.empty(); } private: //队列 std::list
                    
                     m_queue; //互斥锁 std::mutex m_mutex; //不为空时的条件变量 std::condition_variable_any m_notEmpty; //不为满时的条件变量 std::condition_variable_any m_notFull; //队列最大长度 int m_maxsize; }; void func(SynQueue
                     
                       *sq) { int ret; sq->Take(ret); std::cout << ret << std::endl; } int main(int argc, char *argv[]) { //创建线程队列,长度最大为20 SynQueue
                      
                       syn(20); //放置数据对象 for(int i = 0; i < 10; i++) { syn.Put(i); } std::cout << syn.Size() << std::endl; //线程不能拷贝,用容器和智能指针来管理线程生存 std::vector
                       
                        > tvec; //多循环一次,资源不足,阻塞最后一个线程,在后面添加一个资源,看该线程是否会被唤醒执行。 for(int i = 0; i < 11; i++) { //创建线程并且将管理线程的智能指针保存到容器中 tvec.push_back(std::make_shared
                        
                         (func, &syn)); //变为后台线程 tvec[i]->detach(); } sleep(10); //添加一个资源 syn.Put(11); sleep(10); return EXIT_SUCCESS; }
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
          
         
        
       
      
     
    
   

运行结果:
这里写图片描述


4.原子变量

原子变量,为原子操作,不需要加锁
std::atomic
详情可参考,这里仅简单举例用法
cppreference atomic

代码:

#include 
   
     #include 
    
      #include 
     
       #include 
      
        #include 
       
         #include 
        
          #include 
         
           //创建int类型的原子变量 std::atomic
          
           atc(0); void func() { std::cout << atc << std::endl; 原子变量自增 atc++; } int main(int argc, char *argv[]) { std::vector
           
            tvec; for(int i = 0; i < 10; i++) { std::thread t(func); //线程对象移动语义 tvec.push_back(std::move(t)); tvec[i].join(); } return EXIT_SUCCESS; }
           
          
         
        
       
      
     
    
   

?

-->

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: