Libevent原始碼分析-----多執行緒、鎖、條件變數(一)
Libevent提供給使用者的可見多執行緒API都在thread.h檔案中。在這個檔案提供的API並不多。基本上都是一些定製函式,像前面幾篇博文說到的,可以為Libevent定製使用者自己的多執行緒函式。
開啟多執行緒:
Libevent預設是不開啟多執行緒的,也沒有鎖、條件變數這些東西。這點和前面部落格說到的"沒有定製就用Libevent預設提供",有所不同。只有當你呼叫了evthread_use_windows_threads()或者evthread_use_pthreads()或者呼叫evthread_set_lock_callbacks函式定製自己的多執行緒、鎖、條件變數才會開啟多執行緒功能。其實,前面的那兩個函式其內部實現也是定製,在函式的內部,Libevent封裝的一套Win32執行緒、pthreads執行緒。然後呼叫evthread_set_lock_callbacks函式,進行定製。
thread.h檔案只提供了定製執行緒的介面,並沒有提供使用執行緒介面。這點很像前面說到的Libevent日誌和記憶體分配。其實這也很好理解。因為都是你提供定製的執行緒函式。你都能提供了,你肯定有辦法使用,沒必要要Libevent提供一些API給你使用。
如果使用者為libevent開啟了多執行緒,那麼libevent裡面的函式就會變成執行緒安全的。此時主執行緒在使用event_base_dispatch,別的執行緒是可以執行緒安全地使用event_add把一個event新增到主執行緒的event_base中。具體的工作原理可以參考《evthread_notify_base通知主執行緒
鎖和條件變數結構體:
Libevent允許使用者定製自己的鎖和條件變數。其實現原理和前面說到的日誌和記憶體分配一樣,都是內部有一個全域性變數。定製自己的鎖和條件變數,就是對這個全域性變數進行賦值。
鎖結構:
目前Libevent支援的locktype(鎖型別)有三種://thread.h檔案 struct evthread_lock_callbacks { //版本號,設定為巨集EVTHREAD_LOCK_API_VERSION int lock_api_version; //支援的鎖型別,有普通鎖,遞迴鎖,讀寫鎖三種 unsigned supported_locktypes; //分配一個鎖變數(指標型別),因為不同的平臺鎖變數是不同的型別 //所以用這個通用的void*型別 void *(*alloc)(unsigned locktype); void (*free)(void *lock, unsigned locktype); int (*lock)(unsigned mode, void *lock); int (*unlock)(unsigned mode, void *lock); };
- 普通鎖, 值為0
- 遞迴鎖, 值為EVTHREAD_LOCKTYPE_RECURSIVE
- 讀寫鎖, 值為EVTHREAD_LOCKTYPE_READWRITE
引數mode(鎖模式)則取下面的值:
- EVTHREAD_READ:僅用於讀寫鎖:為讀操作請求或者釋放鎖
-
EVTHREAD_WRITE:僅用於讀寫鎖:為寫操作請求或者釋放鎖
-
EVTHREAD_TRY:僅用於鎖定:僅在可以立刻鎖定的時候才請求鎖定
雖然Libevent提供了這些鎖型別和mode型別,但實際上是否支援這些型別完全是由所定製的執行緒鎖決定的。Libevent提供的pthreads執行緒鎖和WIN32執行緒鎖就只支援其中的一部分。具體是哪些下面會說到。
條件變數結構:
//thread.h檔案
struct evthread_condition_callbacks {
//版本號,設定為EVTHREAD_CONDITION_API_VERSION巨集
int condition_api_version;
void *(*alloc_condition)(unsigned condtype);
void (*free_condition)(void *cond);
int (*signal_condition)(void *cond, int broadcast);
int (*wait_condition)(void *cond, void *lock,
const struct timeval *timeout);
};
條件變數的版本為EVTHREAD_CONDITION_API_VERSION時,alloc_condition的引數取0。奇怪的是,Libevent並沒有提供其他的版本號。前面的執行緒鎖也是隻提供給了一個版本號。
signal_condition的第一個引數為alloc_condition的返回值,第二個引數指明喚醒多少個等待的執行緒。當broadcast取1時,喚醒所有的執行緒。取其他值時,只喚醒其中一個執行緒。
wait_condition的第二個引數為前面執行緒鎖evthread_lock_callbacks結構中的alloc指標函式的返回值。熟悉條件變數的讀者,這點還是比較容易懂的。對於第三個引數,和pthread_cond_timedwait有所不同。pthread_cond_timedwait的時間是絕對時間,這裡的timeout則是等待的時間,所以千萬不要用一個絕對時間作為引數值,不然等到老都等不到超時。如果該引數為NULL,那麼就沒有超時,將死等下去,直到另外的執行緒呼叫了signal_condition。
Libevent封裝的多執行緒:
說了這麼多,其實對於使用者來說,如果想讓Libevent支援多執行緒。Windows使用者直接呼叫evthread_use_windows_threads(),遵循pthreads執行緒的系統直接呼叫evthread_use_pthreads()就可以了。其他什麼東西都不需要做了。還有一點要注意的是,這兩個函式要在程式碼的一開始就呼叫, 必須在event_base_new函式之前呼叫。好了,現在還是研究程式碼吧。 下面看一下evthread_use_pthreads()函式。
//evthread_pthreads.c檔案
int
evthread_use_pthreads(void)
{
//結構體中做一些函式指標作為引數。這些函式都是定義在evthread_pthread.c檔案中
struct evthread_lock_callbacks cbs = {
EVTHREAD_LOCK_API_VERSION,
EVTHREAD_LOCKTYPE_RECURSIVE,
evthread_posix_lock_alloc,//函式指標
evthread_posix_lock_free,//函式指標
evthread_posix_lock,//函式指標
evthread_posix_unlock//函式指標
};
struct evthread_condition_callbacks cond_cbs = {
EVTHREAD_CONDITION_API_VERSION,
evthread_posix_cond_alloc,
evthread_posix_cond_free,
evthread_posix_cond_signal,
evthread_posix_cond_wait
};
/* Set ourselves up to get recursive locks. */
if (pthread_mutexattr_init(&attr_recursive))
return -1;
if (pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE))
return -1;
evthread_set_lock_callbacks(&cbs); //定製鎖操作
evthread_set_condition_callbacks(&cond_cbs); //定製條件變數操作
evthread_set_id_callback(evthread_posix_get_id); //設定可以獲取執行緒ID的回撥函式
return 0;
}
函式一開始就定義並初始化了一個evthread_lock_callbacks結構和一個evthread_condition_callbacks結構。然後用之去進行定製。
程式碼中的attr_recursive是一個鎖屬性pthread_mutexattr_t型別的全域性變數。在這個函式中,它被設定成具有遞迴屬性。在申請鎖時,可以看到其作用。
static void *
evthread_posix_lock_alloc(unsigned locktype)
{
pthread_mutexattr_t *attr = NULL;
pthread_mutex_t *lock = mm_malloc(sizeof(pthread_mutex_t));
if (!lock)
return NULL;
if (locktype & EVTHREAD_LOCKTYPE_RECURSIVE)
attr = &attr_recursive;
if (pthread_mutex_init(lock, attr)) {
mm_free(lock);
return NULL;
}
return lock;
}
可以看到這個全域性變數是用於設定遞迴鎖的。從這個函式可以看到,Libevent提供的pthreads版本鎖只支援遞迴鎖和普通非遞迴鎖,並不支援讀寫鎖。當然你可以提供一套支援讀寫鎖的鎖操作。閱讀Libevent提供的WIN32版本鎖程式碼,也可以看到並不支援讀寫鎖。值得注意的是,WIN32的鎖預設是具有遞迴功能的,無需用EVTHREAD_LOCKTYPE_RECURSIVE作引數值。
前面還說到Libevent提供了EVTHREAD_READ、EVTHREAD_READ、EVTHREAD_TRY三種鎖模式(mode)。但在Libevent提供的pthreads版本鎖中,只在evthread_posix_lock函式中使用到這些巨集。
static int
evthread_posix_lock(unsigned mode, void *_lock)
{
pthread_mutex_t *lock = _lock;
if (mode & EVTHREAD_TRY)
return pthread_mutex_trylock(lock);
else
return pthread_mutex_lock(lock);
}
可以看到,它僅僅支援EVTHREAD_TRY這個鎖模式。WIN32版本也是如此。
條件變數也簡單地對系統native的條件進行一些簡單的封裝。這裡就不多說了。在Windows中,因為在Windows Vista之前的Windows 作業系統並不支援提供條件變數,此時Libevent就使用Windows提供的EVENT進行一些封裝來實現條件變數的功能。如果所在的Windows系統支援條件變數,Libevent將優先使用Windows本身提供的條件變數。這點可以在evthread_use_windows_threads函式看到。
//evthread_win32.c檔案
int
evthread_use_windows_threads(void)
{
struct evthread_lock_callbacks cbs = {
EVTHREAD_LOCK_API_VERSION,
EVTHREAD_LOCKTYPE_RECURSIVE,
evthread_win32_lock_create,
evthread_win32_lock_free,
evthread_win32_lock,
evthread_win32_unlock
};
struct evthread_condition_callbacks cond_cbs = {
EVTHREAD_CONDITION_API_VERSION,
evthread_win32_cond_alloc,
evthread_win32_cond_free,
evthread_win32_cond_signal,
evthread_win32_cond_wait
};
#ifdef WIN32_HAVE_CONDITION_VARIABLES //有內建的條件變數功能
struct evthread_condition_callbacks condvar_cbs = {
EVTHREAD_CONDITION_API_VERSION,
evthread_win32_condvar_alloc,
evthread_win32_condvar_free,
evthread_win32_condvar_signal,
evthread_win32_condvar_wait
};
#endif
evthread_set_lock_callbacks(&cbs);
evthread_set_id_callback(evthread_win32_get_id);
//優先使用Windows自身提供的條件變數
#ifdef WIN32_HAVE_CONDITION_VARIABLES
if (evthread_win32_condvar_init()) {
evthread_set_condition_callbacks(&condvar_cbs);
return 0;
}
#endif
evthread_set_condition_callbacks(&cond_cbs);
return 0;
}
一旦使用者呼叫evthread_use_windows_threads()或者evthread_use_pthreads()函式,那麼使用者就為Libevent定製了自己的執行緒鎖操作。Libevent的其他程式碼中,如果需要用到鎖,就會去呼叫這些執行緒鎖操作。在實現上,當呼叫evthread_use_windows_threads()或者evthread_use_pthreads()函式時,兩個函式的內部都會呼叫evthread_set_lock_callbacks函式。而這個設定函式會把前面兩個evthread_use_xxx函式中定義的cbs變數值複製到一個evthread_lock_callbacks型別的_evthread_lock_fns全域性變數儲存起來。以後,Libevent需要用到多執行緒鎖操作,直接訪問這個_evthread_lock_fn變數即可。對於條件變數,也是用這樣方式實現的。
定製的順序:
前面的一些博文和這篇都說到了使用者可以定製自己的操作,比如記憶體分配、日誌記錄、執行緒鎖。這些定製都應該放在程式碼的最前面,即不能在使用Libevent的event、event_base這些結構體之後。因為這些結構體會使用到記憶體分配、日誌記錄、執行緒鎖的。而這三者的定製順序應該是:記憶體分配->日誌記錄->執行緒鎖。參考:
相關推薦
Libevent原始碼分析-----多執行緒、鎖、條件變數(一)
Libevent提供給使用者的可見多執行緒API都在thread.h檔案中。在這個檔案提供的API並不多。基本上都是一些定製函式,像前面幾篇博文說到的,可以為Libevent定製使用者自己的多執行緒函式。 開啟多執行緒: Libeve
java多執行緒物件鎖、類鎖、同步機制詳解
1.在java多執行緒程式設計中物件鎖、類鎖、同步機制synchronized詳解: 物件鎖:在java中每個物件都有一個唯一的鎖,物件鎖用於物件例項方法或者一個物件例項上面的。 類鎖:是用於一個類靜態方法或者class物件的,一個
018.多執行緒-悲觀鎖、樂觀鎖、重入鎖、讀寫鎖、自旋鎖、CAS無鎖機制
悲觀鎖(Pessimistic Lock) 顧名思義,就是很悲觀。每次去拿資料的時候都認為別人會修改,所以都會上鎖。這樣別人想拿這個資料就會阻塞(block)直到它拿到鎖。傳統的關係型資料庫裡面就用到了很多這種鎖機制。比如:行鎖,表鎖,讀鎖,寫鎖等,都是在做操作之前先上鎖。
JDK原始碼分析--多執行緒同步工具CountDownLatch類
CountDownLatch類運用了java開發模式中的策略模式。對執行緒作用的是CountDownLatch類中的內部類Sync。 Sync類繼承了AbstractQueuedSynchronizer類,AbstractQueuedSynchronizer類是jdk多執行
Linux作業系統下的多執行緒程式設計詳細解析----條件變數pthread_cond_t
在多執行緒程式設計下,常常出現A執行緒要等待B執行緒條件完成後再繼續進行,這裡等待方式有兩種: 1.使用鎖+輪詢 使用這種方法可以很簡單的實現,但是會有一定的效能消耗,其還有一個點要好好把握,就是一次輪詢沒有結果後相隔多久進行下一次的輪詢,間隔時間太短,消耗
Linux多執行緒實踐(8) --Posix條件變數解決生產者消費者問題
Posix條件變數int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); int pthread_cond_destroy(pthread_cond_t *cond); int
c++11多執行緒 生產者-消費者模型/條件變數condition_variable
1.生產者消費者模型: 在工作中,大家可能會碰到這樣一種情況:某個模組負責產生資料,這些資料由另一個模組來負責處理(此處的模組是廣義的,可以是類、函式、執行緒、程序等)。產生資料的 模組,就形象地稱為生產者;而處理資料的模組,就稱為消費者。在生產者與消費者之間在加個緩衝區
Linux C語言多執行緒庫Pthread中條件變數的的正確用法逐步詳解
(本文的讀者定位是瞭解Pthread常用多執行緒API和Pthread互斥鎖,但是對條件變數完全不知道或者不完全瞭解的人群。如果您對這些都沒什麼概念,可能需要先了解一些基礎知識) Pthread庫的條件變數機制的主要API有三個: int pthread_cond_w
Linux多執行緒程式設計詳細解析----條件變數 pthread_cond_t
Linux作業系統下的多執行緒程式設計詳細解析----條件變數 1.初始化條件變數pthread_cond_init #include <pthread.h> int pthread_cond_init(pthread_cond_t *cv, const pth
C++11多執行緒---互斥量、鎖、條件變數的總結
關於互斥量std::mutex的總結 互斥量用於組成程式碼的臨界區。C++的多執行緒模型是基於記憶體的,或者說是基於程式碼片段的,這和我們作業系統學習的臨界區概念基本一致,但是與Golang不同,Golang是基於訊息模型的。 一個std::mutex的lock()和unlock
HashMap原始碼及多執行緒併發問題深度分析
以前只知道HashMap是執行緒不安全的,拿來就用,也不會考慮會出現什麼後果,直到最近在學習中終於暴露出了HashMap的短板出來,可又百思不得其解,於是在網上拜讀了若干大牛有關HashMap的分析文章,發現他們其實寫於很早之前,而HashMap的原始碼都已
java面試/筆試題目之多執行緒及鎖 (持續更新中)
前言:這一模組可以參照徐劉根大佬的部落格。 一.執行緒和程序的概念、並行和併發的概念 1.程序:是計算機中的程式關於某資料集合上的一次執行活動,是系統 進行資源分配和排程的基本單位,是作業系統結構的基礎。程式是指令、資料及其組織形式的描述,程序是程式的實體。 2.執行緒:是程式執行流的
多執行緒2-synchronized、lock
1、什麼時候會出現執行緒安全問題? 在多執行緒程式設計中,可能出現多個執行緒同時訪問同一個資源,可以是:變數、物件、檔案、資料庫表等。此時就存在一個問題: 每個執行緒執行過程是不可控的,可能導致最終結果與實際期望結果不一致或者直接導致程式出錯。 如我們在第一篇部落格中出現的count--的問
Java多執行緒-無鎖
1 無鎖類的原理詳解 1.1 CAS CAS演算法的過程是這樣:它包含3個引數CAS(V,E,N)。V表示要更新的變數,E表示預期值,N表示新值。僅當V 值等於E值時,才會將V的值設為N,如果V值和E值不同,則說明已經有其他執行緒做了更新,則當前執行緒什麼 都不做。最後,CAS返
pytho---多執行緒(鎖)
from multiprocessing import Process import time class MyProcess(Process): def init(self): super(MyProcess, self).init() #self.name = name def
34-多執行緒--死鎖+執行緒間通訊+等待喚醒機制+多生產者多消費者問題
一、死鎖 1、死鎖的常見情形之一:同步的巢狀 說明:同步的巢狀,至少得有兩個鎖,且第一個鎖中有第二個鎖,第二個鎖中有第一個鎖。eg:同步程式碼塊中有同步函式,同步函式中有同步程式碼塊。下面的例子,同步程式碼塊的鎖是obj,同步函式的鎖是this。t1執行緒先執行同步程式碼塊,獲取鎖obj,需
多執行緒和鎖和原子操作和記憶體柵欄(二)
這裡記錄下各種鎖的使用和使用場景,在多執行緒場景開發時,我們經常遇到多個執行緒同時讀寫一塊資源爭搶一塊資源的情況,比如同時讀寫同一個欄位屬性,同時對某個集合進行增刪改查,同時對資料庫進行讀寫(這裡
多執行緒和鎖和原子操作和記憶體柵欄(一)
執行緒的定義是執行流的最小單元,而程序是一個邏輯執行緒容器,用來隔離執行緒。 Task類封裝了執行緒池執行緒,啟動的所有線都由執行緒池管理,他提供了很多使用方便的API函式,使多執行緒開發變得容易。 上述程式碼中我啟動了一個執行緒,並在執行緒方法中使用了非同步關鍵字,非同步方法實現了一個狀態
C#多執行緒基礎(多執行緒的優先順序、狀態、同步)
一、關於多執行緒的優先順序、狀態、同步指令碼如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System
java多執行緒之鎖機制二
網上看到一個題目,題目是這樣:Java多執行緒,啟動四個執行緒,兩個執行加一,另外兩個執行減一。 針對該問題寫了一個程式,測試通過,如下: class Sync { static int count = 0; public void add() {