某些结构体内在地是单线程的:同时在多个线程中使用它们总是不安全的。
某些结构体具有可选的锁:可以告知 libevent 是否需要在多个线程中使用每个对象。
某些结构体总是锁定的:如果 libevent 在支持锁的配置下运行,在多个线程中使用它们总是安全的。
目前默认编译生成的libevent是支持多线程的,这一点可以从他的cmake过程文件(build/CMakeCache.txt
)中看出:
EVENT__DISABLE_THREAD_SUPPORT:BOOL=OFF
之后这个宏会在libevent-2.1.12-stable/include/event2/thread.h
这个提供给用户的头文件中用到:
#if !defined(EVENT__DISABLE_THREAD_SUPPORT) || defined(EVENT_IN_DOXYGEN_)
#define EVTHREAD_LOCK_API_VERSION 1
#define EVTHREAD_LOCKTYPE_RECURSIVE 1
#define EVTHREAD_LOCKTYPE_READWRITE 2
struct evthread_lock_callbacks {
int lock_api_version;
unsigned supported_locktypes;
void *(*alloc)(unsigned locktype);
void (*free)(void *lock, unsigned locktype);
int (*lock)(unsigned mode, void *lock);
int (*unlock)(unsigned mode, void *lock);
};
EVENT2_EXPORT_SYMBOL
int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);
#define EVTHREAD_CONDITION_API_VERSION 1
struct timeval;
struct evthread_condition_callbacks {
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);
};
EVENT2_EXPORT_SYMBOL
int evthread_set_condition_callbacks(
const struct evthread_condition_callbacks *);
EVENT2_EXPORT_SYMBOL
void evthread_set_id_callback(
unsigned long (*id_fn)(void));
#if (defined(_WIN32) && !defined(EVENT__DISABLE_THREAD_SUPPORT)) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Windows builtin locking and thread ID
functions. Unavailable if Libevent is not built for Windows.
@return 0 on success, -1 on failure. */
EVENT2_EXPORT_SYMBOL
int evthread_use_windows_threads(void);
/**
Defined if Libevent was built with support for evthread_use_windows_threads()
*/
#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED 1
#endif
#if defined(EVENT__HAVE_PTHREADS) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Pthreads locking and thread ID functions.
Unavailable if Libevent is not build for use with pthreads. Requires
libraries to link against Libevent_pthreads as well as Libevent.
@return 0 on success, -1 on failure. */
EVENT2_EXPORT_SYMBOL
int evthread_use_pthreads(void);
/** Defined if Libevent was built with support for evthread_use_pthreads() */
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
#endif
/** Enable debugging wrappers around the current lock callbacks. If Libevent
* makes one of several common locking errors, exit with an assertion failure.
*
* If you're going to call this function, you must do so before any locks are
* allocated.
**/
EVENT2_EXPORT_SYMBOL
void evthread_enable_lock_debugging(void);
/* Old (misspelled) version: This is deprecated; use
* evthread_enable_log_debugging instead. */
EVENT2_EXPORT_SYMBOL
void evthread_enable_lock_debuging(void);
#endif /* EVENT__DISABLE_THREAD_SUPPORT */
从具体的代码可以看出,EVENT__DISABLE_THREAD_SUPPORT
宏直接关系到libevent是否支持多线程,以及用户能够定制自己的多线程相关函数。
同时在thread.h
文件中,还可以看到其对不同系统线程的支持(windows线程和pthread线程)。
#if (defined(_WIN32) && !defined(EVENT__DISABLE_THREAD_SUPPORT)) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Windows builtin locking and thread ID
functions. Unavailable if Libevent is not built for Windows.
@return 0 on success, -1 on failure. */
EVENT2_EXPORT_SYMBOL
int evthread_use_windows_threads(void);
/**
Defined if Libevent was built with support for evthread_use_windows_threads()
*/
#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED 1
#endif
#if defined(EVENT__HAVE_PTHREADS) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Pthreads locking and thread ID functions.
Unavailable if Libevent is not build for use with pthreads. Requires
libraries to link against Libevent_pthreads as well as Libevent.
@return 0 on success, -1 on failure. */
EVENT2_EXPORT_SYMBOL
int evthread_use_pthreads(void);
/** Defined if Libevent was built with support for evthread_use_pthreads() */
#define EVTHREAD_USE_PTHREADS_IMPLEMENTED 1
#endif
只有当你调用了evthread_use_windows_threads()
或者evthread_use_pthreads()
或者调用evthread_set_lock_callbacks
函数定制自己的多线程、锁、条件变量才会开启多线程功能。其实,前面的那两个函数其内部实现也是定制,在函数的内部,libevent封装的一套Win32线程、pthreads线程。然后调用evthread_set_lock_callbacks
函数,进行定制。以evthread_use_pthreads
函数为例(libevent-2.1.12-stable/evthread_pthread.c
),如果想了解windows可以查看evthread_use_windows_threads
函数(libevent-2.1.12-stable/evthread_win32.c
):
int
evthread_use_pthreads(void)
{
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);
return 0;
}
从上面的代码可以看出,定制线程其实就定义了三类相关操作:
evthread_set_lock_callbacks
evthread_set_condition_callbacks
evthread_set_id_callback
下面针对这三种操作详细说明
evthread_lock_callbacks
结构体:/** This structure describes the interface a threading library uses for
* locking. It's used to tell evthread_set_lock_callbacks() how to use
* locking on this platform.
*/
struct evthread_lock_callbacks {
/** The current version of the locking API. Set this to
* EVTHREAD_LOCK_API_VERSION */
int lock_api_version;
/** Which kinds of locks does this version of the locking API
* support? A bitfield of EVTHREAD_LOCKTYPE_RECURSIVE and
* EVTHREAD_LOCKTYPE_READWRITE.
*
* (Note that RECURSIVE locks are currently mandatory, and
* READWRITE locks are not currently used.)
**/
unsigned supported_locktypes;
/** Function to allocate and initialize new lock of type 'locktype'.
* Returns NULL on failure. */
void *(*alloc)(unsigned locktype);
/** Funtion to release all storage held in 'lock', which was created
* with type 'locktype'. */
void (*free)(void *lock, unsigned locktype);
/** Acquire an already-allocated lock at 'lock' with mode 'mode'.
* Returns 0 on success, and nonzero on failure. */
int (*lock)(unsigned mode, void *lock);
/** Release a lock at 'lock' using mode 'mode'. Returns 0 on success,
* and nonzero on failure. */
int (*unlock)(unsigned mode, void *lock);
};
evthread_lock_callbacks
结构体描述的锁回调函数及其能力。
lock_api_version
字段必须设置为 EVTHREAD_LOCK_API_VERSION
。supported_locktypes
字段为 EVTHREAD_LOCKTYPE_*
常量(EVTHREAD_LOCKTYPE_RECURSIVE
、EVTHREAD_LOCKTYPE_READWRITE
)的组合以描述支持的锁类型(在2.0.4-alpha版本中 , EVTHREAD_LOCK_RECURSIVE是必须的,EVTHREAD_LOCK_READWRITE 则没有使用)。可识别的锁类型有:
0:通常的,不必递归的锁。
EVTHREAD_LOCKTYPE_RECURSIVE
:不会阻塞已经持有它的线程的锁。一旦持有它的线程进行原来锁定次数的解锁,其他线程立刻就可以请求它了。
EVTHREAD_LOCKTYPE_READWRITE
:可以让多个线程同时因为读而持有它,但是任何时刻只有一个线程因为写而持有它。写操作排斥所有读操作。
可识别的锁模式有:
EVTHREAD_READ
:仅用于读写锁:为读操作请求或者释放锁
EVTHREAD_WRITE
:仅用于读写锁:为写操作请求或者释放锁
EVTHREAD_TRY
:仅用于锁定:仅在可以立刻锁定的时候才请求锁定
evthread_condition_callbacks
结构体:
/** This structure describes the interface a threading library uses for
* condition variables. It's used to tell evthread_set_condition_callbacks
* how to use locking on this platform.
*/
struct evthread_condition_callbacks {
/** The current version of the conditions API. Set this to
* EVTHREAD_CONDITION_API_VERSION */
int condition_api_version;
/** Function to allocate and initialize a new condition variable.
* Returns the condition variable on success, and NULL on failure.
* The 'condtype' argument will be 0 with this API version.
*/
void *(*alloc_condition)(unsigned condtype);
/** Function to free a condition variable. */
void (*free_condition)(void *cond);
/** Function to signal a condition variable. If 'broadcast' is 1, all
* threads waiting on 'cond' should be woken; otherwise, only on one
* thread is worken. Should return 0 on success, -1 on failure.
* This function will only be called while holding the associated
* lock for the condition.
*/
int (*signal_condition)(void *cond, int broadcast);
/** Function to wait for a condition variable. The lock 'lock'
* will be held when this function is called; should be released
* while waiting for the condition to be come signalled, and
* should be held again when this function returns.
* If timeout is provided, it is interval of seconds to wait for
* the event to become signalled; if it is NULL, the function
* should wait indefinitely.
*
* The function should return -1 on error; 0 if the condition
* was signalled, or 1 on a timeout. */
int (*wait_condition)(void *cond, void *lock,
const struct timeval *timeout);
};
evthread_condition_callbacks
结构体描述了与条件变量相关的回调函数。
condition_api_version
字段必须设置为 EVTHREAD_CONDITION_API_VERSION
。alloc_condition
函数必须返回到新条件变量的指针。它接受0作为其参数。free_condition
函数必须释放条件变量持有的存储器和资源.wait_condition
函数要求三个参数:一个由alloc_condition
分配的条件变量,一个由你提供的 evthread_lock_callbacks
.alloc
函数分配的锁,以及一个可选的超时值。调用本函数时,必须已经持有参数指定的锁;本函数应该释放指定的锁,等待条件变量成为授信状态,或者直到指定的超时时间已经流逝(可选 )。wait_condition
应该在错误时返回-1,条件变量授信时返回0,超时时返回1。返回之前,函数应该确定其再次持有锁。signal_condition
函数应该唤醒等待该条件变量的某个线程(broadcast 参数为 false 时),或者唤醒等待条件变量的所有线程(broadcast 参数为 true时)。只有在持有与条件变量相关的锁的时候,才能够进行这些操作。关于条件变量的更多信息,请查看 pthreads 的 pthread_cond_*函数文档,或者 Windows的 CONDITION_VARIABLE(Windows Vista 新引入的)函数文档。
id_fn
/**
Sets the function for determining the thread id.
@param base the event base for which to set the id function
@param id_fn the identify function Libevent should invoke to
determine the identity of a thread.
*/
EVENT2_EXPORT_SYMBOL
void evthread_set_id_callback(
unsigned long (*id_fn)(void));
id_fn
参数必须是一个函数,它返回一个无符号长整数,标识调用此函数的线程。对于相同线程,这个函数应该总是返回同样的值;而对于同时调用该函数的不同线程,必须返回不同的值。
如果用户为libevent开启了多线程,那么libevent里面的函数就会变成线程安全的。此时主线程在使用event_base_dispatch,别的线程是可以线程安全地使用event_add把一个event添加到主线程的event_base中。
libevent定制多线程其实就是定制多线程中会使用到的锁、条件变量、id,这点可以从libevent-2.1.12-stable/include/event2/thread.h
文件中知道:
evthread_set_lock_callbacks
/** Sets a group of functions that Libevent should use for locking.
* For full information on the required callback API, see the
* documentation for the individual members of evthread_lock_callbacks.
*
* Note that if you're using Windows or the Pthreads threading library, you
* probably shouldn't call this function; instead, use
* evthread_use_windows_threads() or evthread_use_posix_threads() if you can.
*/
EVENT2_EXPORT_SYMBOL
int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);
evthread_set_condition_callbacks
/** Sets a group of functions that Libevent should use for condition variables.
* For full information on the required callback API, see the
* documentation for the individual members of evthread_condition_callbacks.
*
* Note that if you're using Windows or the Pthreads threading library, you
* probably shouldn't call this function; instead, use
* evthread_use_windows_threads() or evthread_use_pthreads() if you can.
*/
EVENT2_EXPORT_SYMBOL
int evthread_set_condition_callbacks(
const struct evthread_condition_callbacks *);
evthread_set_id_callback
/**
Sets the function for determining the thread id.
@param base the event base for which to set the id function
@param id_fn the identify function Libevent should invoke to
determine the identity of a thread.
*/
EVENT2_EXPORT_SYMBOL
void evthread_set_id_callback(
unsigned long (*id_fn)(void));
一旦用户调用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 函数之前,必须告知libevent 使用哪个锁函数。如果使用 pthreads 库,或者使用 Windows 本地线程代码,那么已经有设置libevent 使用正确的 pthreads 或者 Windows 函数的预定义函数。
为帮助调试锁的使用,libevent 有一个可选的“锁调试”特征。这个特征包装了锁调用,以便捕获典型的锁错误,包括:
解锁并没有持有的锁
重新锁定一个非递归锁
如果发生这些错误中的某一个,libevent 将给出断言失败并且退出。
/** Enable debugging wrappers around the current lock callbacks. If Libevent
* makes one of several common locking errors, exit with an assertion failure.
*
* If you're going to call this function, you must do so before any locks are
* allocated.
**/
EVENT2_EXPORT_SYMBOL
void evthread_enable_lock_debugging(void);
必须在创建或者使用任何锁之前调用这个函数。为安全起见,请在设置完线程函数后立即调用这个函数。