天天看點

[modern c++] 細粒度鎖的concurrency資料結構

前言

中描述的是 serilization 模式的資料結構,其鎖的粒度很粗,雖然說是多線程安全的,但是無法進行并發(concurrency)通路,進而導緻同一時刻隻能有一個線程在操作資料結構。

如果想實作并發通路,一種是使用細粒度鎖的資料結構,另一種就是無鎖的資料結構設計。其實細粒度鎖屬于 “僞并發”,它在是否能真正設計成并發需要視應用場景而定。

雖然都有鎖,粗粒度鎖就是串行化通路,細粒度鎖通過對具體應用場景的分析可以做到一定程度上的 “僞并發”。

局部加鎖的隊列:

#pragma once

#include <queue>
#include <memory>


template <typename T>
class threadsafe_queue 
{
private:
  struct node {
    std::shared_ptr<T> data;

    //為什麼要用unique_ptr,如果想做細粒度的鎖控制,那麼unique_ptr是必須的,細粒度控制意味着局部加鎖,且各個動作之前應當是互不幹擾的
    //比如pop隻操作隊列頭部,push隻操作隊列尾部,這樣的話就可以保證生産者線程(調用push的線程)和消費者線程(調用pop的線程)可以完全并發
    //進行。但是多個生産者之間還是需要同步,多個生産者之間也需要互相同步,且同步使用的就是局部鎖。
    //unique_ptr可以保證指針不會被導出,這樣的話外部就無法對指針額外加鎖和銷毀指針。
    //局部鎖是并發通路的理論基礎。
    //并發通路的核心思想就是通過為固有/常見的資料結構添加一些輔助節點,以實作生産者和消費者的通路隔離,在此基礎上,如果有多個生産者和多個
    //消費者,則隻提供生産者同步鎖和消費者同步鎖即可。
    std::unique_ptr<node> next;     
  };

  std::mutex head_m;
  std::mutex tail_m;
  std::unique_ptr<node> head;
  node* tail;

  node* get_tail() 
  {
    std::lock_guard<std::mutex> lock_tail(tail_m);
    return tail;
  }

  std::unique_ptr<node> pop_head()
  {
    std::lock_guard<std::mutex> lock_head(head_m);

    if (head.get() == get_tail()) {
      return nullptr;
    }

    std::unique_ptr<node> old_head = std::move(head);
    head = std::move(old_head->next);
    return old_head;
  }

public:
  threadsafe_queue()
    :head(new node)
    , tail(head.get())
  {}

  threadsafe_queue(const threadsafe_queue& other) = delete;
  threadsafe_queue& operator=(const threadsafe_queue& other) = delete;

  std::shared_ptr<T> try_pop() 
  {
    std::unique_ptr<node> old_head = pop_head();
    return old_head ? old_head->data : std::shared_ptr<T>();
  }

  void push(T new_value) 
  {
    std::shared_ptr<T> new_data(std::make_shared<T>(std::move(new_value)));
    std::unique_ptr<node> p(new node);
    node* const new_tail = p.get();
    std::lock_guard<std::mutex> tail_lock(tail_m);
    tail->data = new_data;
    tail->next = std::move(p);
    tail = new_tail;
  }
};








      

繼續閱讀