1. 程式人生 > >Libevent原始碼分析-----多執行緒、鎖、條件變數(一)

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允許使用者定製自己的鎖和條件變數。其實現原理和前面說到的日誌和記憶體分配一樣,都是內部有一個全域性變數。定製自己的鎖和條件變數,就是對這個全域性變數進行賦值。

        鎖結構:

//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);
};
        目前Libevent支援的locktype(鎖型別)有三種:
  • 普通鎖, 值為0
  • 遞迴鎖, 值為EVTHREAD_LOCKTYPE_RECURSIVE
  • 讀寫鎖, 值為EVTHREAD_LOCKTYPE_READWRITE
        當用戶定製了自己的執行緒鎖後,就可以用alloc這個函式指標呼叫函式,獲取一個鎖變數指標(在支援pthreads的系統獲得的是pthread_mutex_t型別指標)。引數locktype就是使用者指定的鎖型別。

        引數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變數即可。對於條件變數,也是用這樣方式實現的。

定製的順序:

        前面的一些博文和這篇都說到了使用者可以定製自己的操作,比如記憶體分配、日誌記錄、執行緒鎖。這些定製都應該放在程式碼的最前面,即不能在使用Libeventeventevent_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-synchronizedlock

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() {