可以使用細粒度的鎖來減小佇列的臨界區,這裡使用了乙個dummy node用來進一步減小鎖的臨界區。若要判斷佇列是否為空,只需要執行下述判斷:
head.get() == get_tail()
請注意,因為在進行push的時候需要修改tail,所以對tail的訪問和修改都需要進行加鎖。這裡使用get_tail來封裝這個操作,將鎖的粒度減小到最低。
// lock tail mutex and return tail node
node *get_tail()
對push的操作只涉及到修改tail節點,所以只需要對tail節點進行加鎖。加鎖完成之後就可以修改tail使其指向新的tail節點。
void push(t new_value)
data_cond.notify_one();
}
至於try_pop_head()
為了應對這一種需求,如果隊列為空直接返回,不等待。其操作如下所示:
std::unique_ptrtry_pop_head()
return pop_head();
}
至於wait_and_pop()
需要一直等待,直到彈出佇列中的乙個元素。這裡使用了條件變數,避免執行緒迴圈進行空等待。當然,在push()
的時候,需要配合條件變數通知等待的執行緒。
std::shared_ptrwait_and_pop()
std::unique_ptrwait_pop_head()
// wait for data, return std::unique_lockhead_lock
std::unique_lockwait_for_data()
); return std::move(head_lock);
}
完整的**如下所示:
#pragma once
#include #include templateclass threadsafe_queue
std::shared_ptrtry_pop()
bool try_pop(t &value)
std::shared_ptrwait_and_pop()
void wait_and_pop(t &value)
void push(t new_value)
data_cond.notify_one();
} bool empty()
threadsafe_queue(const threadsafe_queue &) = delete;
threadsafe_queue &operator=(const threadsafe_queue &) = delete;
private:
struct node
; // lock tail mutex and return tail node
node *get_tail()
// pop head node from queue, return old head node
std::unique_ptrpop_head()
// wait for data, return std::unique_lockhead_lock
std::unique_lockwait_for_data()
);return std::move(head_lock);
} std::unique_ptrwait_pop_head()
std::unique_ptrwait_pop_head(t& value)
std::unique_ptrtry_pop_head()
return pop_head();
} std::unique_ptrtry_pop_head(t &value)
value = std::move(*head->data);
return pop_head();
} std::mutex head_mutex; // head mutex
std::unique_ptrhead; // head node
std::mutex tail_mutex; // tail mutex
node *tail; // tail node
std::condition_variable data_cond; // condition variable
};
執行緒安全的hash表是另乙個用於展示細粒度鎖同步的很好的例子。在hash實現之中,使用了基於桶的開鏈hash實現。每個桶對應的鍊錶可以統一使用同乙個鎖進行訪問控制。對鍊錶的修改需要使用寫鎖進行排他的訪問控制,對鍊錶的訪問則使用讀鎖進行保護,這樣就充分利用了讀鎖和寫鎖的區別,將鎖的粒度降到最低,減少可能的資料競爭。
下面的**展示了bucket_type
的用法:
class bucket_type
else
} } private:
typedef std::pairbucket_value;
typedef std::listbucket_data;
typedef typename bucket_data::const_iterator const_bucket_iterator;
typedef typename bucket_data::iterator bucket_iterator;
bucket_data data;
mutable boost::shared_mutex mutex;
const_bucket_iterator find_entry_for(key const& key) const
);} bucket_iterator find_entry_for(key const& key)
);}};
上述**體現了讀鎖和寫鎖的區別,只有在修改鍊錶的時候才使用寫鎖保證一致性,在訪問鍊錶的時候使用讀鎖來遮蔽寫鎖,允許同時訪問。
多個hash桶就組合成了乙個hash table。根據hash規則拿到對應的hash桶,再對桶內的鍊錶進行讀寫操作。
std::vector> buckets;
//獲取對應的hash桶
bucket_type& get_bucket(key const& key) const
hash表剩餘的操作就是對bucket內建函式的轉呼叫。每個bucket有自己的讀寫鎖進行訪問控制。
value value_for(key const& key, value const& default_value=value()) const
對於執行緒安全的鍊錶,也是用dummy node來標誌鍊錶的開頭位置。注意對於遍歷鍊錶的操作,在對對應的鍊錶節點進行操作的時候,一定要持有對應鍊錶節點的鎖,就像這樣:
templatevoid for_each(function f)
}
templatestd::shared_ptrfind_first_if(predicate p)
current = next;
lk = std::move(next_lk);
}return std::shared_ptr();
}
要注意的是,remove操作需要同時持有前後兩個節點的鎖,這樣才能保證重新設定前後節點的時候對應節點不被修改。
templatevoid remove_if(predicate p)
else
}}
對於整個鍊錶的節點的析構也是借助remove_if
完成的。
~threadsafe_list()
);}
完整的鍊錶實現**如下所示:
#include templateclass threadsafe_list
~threadsafe_list()
);} // no copying
threadsafe_list(threadsafe_list&) = delete;
threadsafe_list& operator=(threadsafe_list&) = delete;
// push node in front of the list
void push_front(t const& value)
templatevoid for_each(function f)
}templatestd::shared_ptrfind_first_if(predicate p)
current = next;
lk = std::move(next_lk);
}return std::shared_ptr();
} templatevoid remove_if(predicate p)
else
}} private:
struct node
node(t const& value):
m(),
data(std::make_shared(value)),
next()
};// dummy node, store node data
node head;
};
《c++ 併發程式設計實戰》
併發程式設計 6 基於鎖的併發資料結構設計
主要內容 如果一種資料結構可以被多個執行緒所訪問,其要不就是絕對不變的 其值不會發生變化,並且不需同步 要不程式就要對資料結構進行正確的設計,以確保其能在多執行緒環境下能夠 正確的 同步。一種選擇是使用獨立的互斥量,其可以鎖住需要保護的資料,另一種選擇是設計一種能夠併發訪問的資料結構。第一種使用互斥...
第6章 設計基於鎖的併發資料結構
為併發設計資料結構是為了多個執行緒可以同時更好的使用此結構 設計併發資料結構的準則是什麼 保證黨資料結構不變性被別的執行緒破壞時的狀態不被任何別的執行緒看到。意味著當要在併發中修改此結構中的部分資料時 即資料結構的不變性被破壞 應當通過新增互斥元的方法阻止其他訪問該資料的執行緒訪問 破壞的狀態不被別...
c 併發程式設計(六) 基於鎖的併發資料結構設計
在我們進行併發程式設計的時候,如果多執行緒使用到了資料結構,那麼程式設計過程中需要保證此資料結構的正確同步。有兩種方法 1 選擇單獨的互斥元與外部鎖來保護資料 2 設計乙個可以同時訪問的資料結構 其中前幾節我們涉及到了第一種方法,現在我們重點描述下第二種方法。使用鎖和條件變數的執行緒安全佇列 tem...