C++并发实战17:线程安全的stack和queue(一)

2014-11-24 07:18:37 · 作者: · 浏览: 4

1 线程安全的数据结构有几个可以注意的地方:当一个线程看见invariants时其他线程不会破坏该invariants,比如一个线程在遍历访问vector另一个线程却在修改vector这就破坏了variants;注意数据结构接口引起的竞态,必要的时候将多个操作合并;注意异常的处理;避免局部操作的锁超出其作用范围,否则可能引起死锁;尽可能的缩小临界区。

线程安全的栈关键代码:

#include 
  
   
struct empty_stack: std::exception
{
	const char* what() const throw();
};
template
   
     class threadsafe_stack { private: std::stack
    
      data; mutable std::mutex m; public: threadsafe_stack(){} threadsafe_stack(const threadsafe_stack& other) { std::lock_guard
     
       lock(other.m); data=other.data; } threadsafe_stack& operator=(const threadsafe_stack&) = delete; void push(T new_value) { std::lock_guard
      
        lock(m); data.push(std::move(new_value)); } std::shared_ptr
       
         pop()//top和pop合并,采用shared_ptr返回栈顶元素防止元素构造时发生异常 { std::lock_guard
        
          lock(m); if(data.empty()) throw empty_stack(); std::shared_ptr
         
           const res(std::make_shared
          
           (std::move(data.top())));//make_shared比shared_ptr直接构造效率高 data.pop(); return res; } void pop(T& value)//采用参数引用返回栈顶元素 { std::lock_guard
           
             lock(m); if(data.empty()) throw empty_stack(); value=std::move(data.top()); data.pop(); } bool empty() const { std::lock_guard
            
              lock(m); return data.empty(); } };//这里还是有死锁的可能:栈内元素是用户代码,若该元素在构造或析构时修改栈则可能发生死锁,当然这种设计本身就有一定问题,应该从设计本身下手 
            
           
          
         
        
       
      
     
    
   
  

一个线程安全的queue关键代码:

template
  
   
class threadsafe_queue
{
	private:
		mutable std::mutex mut;
		std::queue
   
     > data_queue;//队里存储的是shared_ptr这样可以保证push和pop操作时不会引起构造或析构异常,队列更加高效 std::condition_variable data_cond;//采用条件变量同步入队和出队操作 public: threadsafe_queue(){} void wait_and_pop(T& value)//直至容器中有元素可以删除 { std::unique_lock
    
      lk(mut); data_cond.wait(lk,[this]{return !data_queue.empty();}); value=std::move(*data_queue.front()); data_queue.pop(); } bool try_pop(T& value)//若队中无元素可以删除则直接返回false { std::lock_guard
     
       lk(mut); if(data_queue.empty()) return false; value=std::move(*data_queue.front()); data_queue.pop(); return true; } std::shared_ptr
      
        wait_and_pop() { std::unique_lock
       
         lk(mut); data_cond.wait(lk,[this]{return !data_queue.empty();}); std::shared_ptr
        
          res=data_queue.front(); data_queue.pop(); return res; } std::shared_ptr
         
           try_pop() { std::lock_guard
          
            lk(mut); if(data_queue.empty()) return std::shared_ptr
           
            (); std::shared_ptr
            
              res=data_queue.front(); data_queue.pop(); return res; } void push(T new_value) { std::shared_ptr
             
               data(std::make_shared
              
               (std::move(new_value)));//数据的构造在临界区外从而缩小临界区,并且不会在临界区抛出异常 std::lock_guard
               
                 lk(mut); data_queue.push(data); data_cond.notify_one(); } bool empty() const { std::lock_guard
                
                  lk(mut); return data_queue.empty(); } };
                
               
              
             
            
           
          
         
        
       
      
     
    
   
  

上面的采用一个mutex保护整个queue使得线程迫使线程顺序化,为了减小锁的粒度,使用链表作为queue的底层数据结构,并采用一个虚拟节点使head和tail分开:

template
  
   
class threadsafe_queue
{
  private:
     struct node
     {
        std::shared_ptr
   
     data;//通过shared_ptr管理资源T,那么资源的初始化和析构都在临界区外进行 std::unique_ptr
    
      next; }; std::mutex head_mutex; std::unique_ptr
     
       head; std::mutex tail_mutex; node* tail; node* get_tail(//返回tail用于判断head==tail { std::lock_guard
      
        tail_lock(tail_mutex); return tail; } std::unique_ptr
       
         pop_head()/删除队首元素并返回该元素 { std::lock_guard
        
          head_lock(head_mutex); if(head.get()==get_tail())//判断队是否为空,get_tail()必选在head_mutex保护下,试想多个线程都在pop那么会出现什么情形  { return nullptr; } std::unique_ptr
         
           old_head=std::move(head); head=std::move(old_head->next); return old_head; } public