libevent 定制——libevent 定制多线程

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 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线程)。

  • 使用windows的线程
#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
  • 使用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.  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
    • 条件变量
    • 创建条件变量
    • 析构条件变量
    • 等待条件变量
    • 触发/广播条件变量
  • 线程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_RECURSIVEEVTHREAD_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 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);

必须在创建或者使用任何锁之前调用这个函数。为安全起见,请在设置完线程函数后立即调用这个函数。

你可能感兴趣的:(libevent,c语言,学习,libevent)