libevent 定制多线程
文章目录
- libevent 定制多线程
- 开启多线程
- 定制多线程
- 调试锁的使用
编写多线程程序的时候,在多个线程中同时访问同样的数据并不总是安全的。
libevent 的结构体在多线程下通常有三种工作方式:
-
某些结构体内在地是单线程的:同时在多个线程中使用它们总是不安全的。
-
某些结构体具有可选的锁:可以告知 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 2struct 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 1struct 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 IDfunctions. 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. Requireslibraries 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线程)。
- 使用windows的线程
#if (defined(_WIN32) && !defined(EVENT__DISABLE_THREAD_SUPPORT)) || defined(EVENT_IN_DOXYGEN_)
/** Sets up Libevent for use with Windows builtin locking and thread IDfunctions. 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
- 使用pthread线程
#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. Requireslibraries 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
- 条件变量
- 创建条件变量
- 析构条件变量
- 等待条件变量
- 触发/广播条件变量
- 线程ID相关操作,
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 则没有使用)。 - alloc函数必须返回指定类型的新锁;
- free函数必须释放指定类型锁持有的所有资源;
- lock函数必须试图以指定模式请求锁定,如果成功则返回0,失败则返回非零;
- unlock 函数必须试图解锁,成功则返回0,否则返回非零。
可识别的锁类型有:
-
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 todetermine 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 todetermine 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);
必须在创建或者使用任何锁之前调用这个函数。为安全起见,请在设置完线程函数后立即调用这个函数。