libevent源码学习7---libevent常用设置

libevent源码学习7—libevent常用设置

libevent 有一些被整个进程共享的、影响整个库的全局设置。必须在调用libevent 库的任何其他部分之前修改这些设置,否则,libevent 会进入不一致的状态。

1. 日志消息回调设置

Libevent 可以记录内部错误和警告。它还记录调试消息(如果它是在日志记录支持下编译的)。默认情况下,这些消息将写入 stderr。您可以通过以下方式覆盖此行为提供您自己的日志记录功能。

#define EVENT_LOG_DEBUG 0
#define EVENT_LOG_MSG   1
#define EVENT_LOG_WARN  2
#define EVENT_LOG_ERR   3

/* Obsolete names: these are deprecated, but older programs might use them.
 * They violate the reserved-identifier namespace. */
#define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG
#define _EVENT_LOG_MSG EVENT_LOG_MSG
#define _EVENT_LOG_WARN EVENT_LOG_WARN
#define _EVENT_LOG_ERR EVENT_LOG_ERR

/**
  A callback function used to intercept Libevent's log messages.

  @see event_set_log_callback
 */
typedef void (*event_log_cb)(int severity, const char *msg);
/**
  Redirect Libevent's log messages.

  @param cb a function taking two arguments: an integer severity between
     EVENT_LOG_DEBUG and EVENT_LOG_ERR, and a string.  If cb is NULL,
	 then the default log is used.

  NOTE: The function you provide *must not* call any other libevent
  functionality.  Doing so can produce undefined behavior.
  */
EVENT2_EXPORT_SYMBOL
void event_set_log_callback(event_log_cb cb);

再次调用event_set_log_callback(),传递参数NULL,就可以恢复默认行为。

2. 致命错误回调设置

libevent 在检测到不可恢复的内部错误时的默认行为是调用exit()或者abort(),退出正在运行的进程。这类错误通常意味着某处有bug,要么在你的代码中,要么在libevent 中。

如果希望更好的处理致命错误,可以为libevent 提供在退出时应该调用的函数,覆盖默认行为。

/**
   A function to be called if Libevent encounters a fatal internal error.

   @see event_set_fatal_callback
 */
typedef void (*event_fatal_cb)(int err);

/**
 Override Libevent's behavior in the event of a fatal internal error.

 By default, Libevent will call exit(1) if a programming error makes it
 impossible to continue correct operation.  This function allows you to supply
 another callback instead.  Note that if the function is ever invoked,
 something is wrong with your program, or with Libevent: any subsequent calls
 to Libevent may result in undefined behavior.

 Libevent will (almost) always log an EVENT_LOG_ERR message before calling
 this function; look at the last log message to see why Libevent has died.
 */
EVENT2_EXPORT_SYMBOL
void event_set_fatal_callback(event_fatal_cb cb);

3. 内存管理回调设置

默认情况下,libevent 使用C库的内存管理函数在堆上分配内存。

通过提供malloc、realloc和free 的替代函数,可以让libevent 使用其他的内存管理器。

希望libevent 使用一个更高效的分配器时;或者希望libevent 使用一个工具分配器,以便检查内存泄漏时,可以调用接口:

/**
 Override the functions that Libevent uses for memory management.

 Usually, Libevent uses the standard libc functions malloc, realloc, and
 free to allocate memory.  Passing replacements for those functions to
 event_set_mem_functions() overrides this behavior.

 Note that all memory returned from Libevent will be allocated by the
 replacement functions rather than by malloc() and realloc().  Thus, if you
 have replaced those functions, it will not be appropriate to free() memory
 that you get from Libevent.  Instead, you must use the free_fn replacement
 that you provided.

 Note also that if you are going to call this function, you should do so
 before any call to any Libevent function that does allocation.
 Otherwise, those functions will allocate their memory using malloc(), but
 then later free it using your provided free_fn.

 @param malloc_fn A replacement for malloc.
 @param realloc_fn A replacement for realloc
 @param free_fn A replacement for free.
 **/
EVENT2_EXPORT_SYMBOL
void event_set_mem_functions(
	void *(*malloc_fn)(size_t sz),
	void *(*realloc_fn)(void *ptr, size_t sz),
	void (*free_fn)(void *ptr));

实现:

void event_set_mem_functions(
	void *(*malloc_fn)(size_t sz), 
	void *(*realloc_fn)(void *ptr, size_t sz), 
	void (*free_fn)(void *ptr))
{
    mm_malloc_fn_ = malloc_fn;
    mm_realloc_fn_ = realloc_fn;
    mm_free_fn_ = free_fn;
}

应用示例:

/* This union's purpose is to be as big as the largest of all the
 * types it contains. */
union alignment {
    size_t sz;
    void *ptr;
    double dbl;
};
/* We need to make sure that everything we return is on the right
   alignment to hold anything, including a double. */
#define ALIGNMENT sizeof(union alignment)
/* We need to do this cast-to-char* trick on our pointers to adjust
   them; doing arithmetic on a void* is not standard. */
#define OUTPTR(ptr) (((char *)ptr) + ALIGNMENT)
#define INPTR(ptr) (((char *)ptr) - ALIGNMENT)
static size_t total_allocated = 0;
static void *replacement_malloc(size_t sz)
{
    void *chunk = malloc(sz + ALIGNMENT);
    if (!chunk)
        return chunk;
    total_allocated += sz;
    *(size_t *)chunk = sz;
    return OUTPTR(chunk);
}
static void *replacement_realloc(void *ptr, size_t sz)
{
    size_t old_size = 0;
    if (ptr)
    {
        ptr = INPTR(ptr);
        old_size = *(size_t *)ptr;
    }
    ptr = realloc(ptr, sz + ALIGNMENT);

    if (!ptr)
        return NULL;
    *(size_t *)ptr = sz;
    total_allocated = total_allocated - old_size + sz;
    return OUTPTR(ptr);
}
static void replacement_free(void *ptr)
{
    ptr = INPTR(ptr);
    total_allocated -= *(size_t *)ptr;
    free(ptr);
}
void start_counting_bytes(void)
{
    event_set_mem_functions(replacement_malloc, replacement_realloc, replacement_free);
}

替换内存管理函数影响 libevent 随后的所有分配、调整大小和释放内存操作。所以,必须保证在调用任何其他 libevent 函数之前进行替换。否则,libevent 可能用你的 free 函数释放用C库的malloc 分配的内存。

4. 锁和线程的设置

编写多线程程序的时候,在多个线程中同时访问同样的数据并不总是安全的。

libevent 的结构体在多线程下通常有三种工作方式:

  • 某些结构体内在地是单线程的:同时在多个线程中使用它们总是不安全的。
  • 某些结构体具有可选的锁:可以告知 libevent 是否需要在多个线程中使用每个对象。
  • 某些结构体总是锁定的:如果 libevent 在支持锁的配置下运行,在多个线程中使用它们总是安全的。

为获取锁,在调用分配需要在多个线程间共享的结构体的 libevent 函数之前,必须告知 libevent 使用哪个锁函数。

如果使用 pthreads 库,或者使用 Windows 本地线程代码,已经有设置 libevent 使用正确的 pthreads 或者 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
#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_set_lock_callbacksevthread_set_id_callback 接口告知 libevent 这些函数

/** 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);
};

/** 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 *);

/**
   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_lock_callbacks 结构体描述的锁回调函数及其能力。对于上述版本 ,lock_api_version 字段必须设置为 EVTHREAD_LOCK_API_VERSION。必须设置supported_locktypes 字段为 EVTHREAD_LOCKTYPE_*常量的组合以描述支持的锁类型
( 在 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:仅用于锁定:仅在可以立刻锁定的时候才请求锁定id_fn 参数必须是一个函数,它返回一个无符号长整数,标识调用此函数的线程。对于相同线程,这个函数应该总是返回同样的值;而对于同时调用该函数的不同线程,必须返回不同的值。

既然有了锁那就一定有条件变量

/** 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);
};

/** 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_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 新引入的)函数文档。

5. 调试锁的使用

为帮助调试锁的使用,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);

/* Old (misspelled) version: This is deprecated; use
 * evthread_enable_log_debugging instead. */
EVENT2_EXPORT_SYMBOL
void evthread_enable_lock_debuging(void);

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

6. 调试事件的使用

libevent 可以检测使用事件时的一些常见错误并且进行报告。这些错误包括:

  • 将未初始化的 event 结构体当作已经初始化的
  • 试图重新初始化未决的 event 结构体

跟踪哪些事件已经初始化需要使用额外的内存和处理器时间,所以只应该在真正调试程序的时候才启用调试模式。

/**
 * Enable some relatively expensive debugging checks in Libevent that
 * would normally be turned off.  Generally, these checks cause code that
 * would otherwise crash mysteriously to fail earlier with an assertion
 * failure.  Note that this method MUST be called before any events or
 * event_bases have been created.
 *
 * Debug mode can currently catch the following errors:
 *    An event is re-assigned while it is added
 *    Any function is called on a non-assigned event
 *
 * Note that debugging mode uses memory to track every event that has been
 * initialized (via event_assign, event_set, or event_new) but not yet
 * released (via event_free or event_debug_unassign).  If you want to use
 * debug mode, and you find yourself running out of memory, you will need
 * to use event_debug_unassign to explicitly stop tracking events that
 * are no longer considered set-up.
 *
 * @see event_debug_unassign()
 */
EVENT2_EXPORT_SYMBOL
void event_enable_debug_mode(void);

必须在创建任何 event_base 之前调用这个函数。如果在调试模式下使用大量由 event_assign(而不是 event_new)创建的事件,程序可能会耗尽内存,这是因为没有方式可以告知 libeventevent_assign 创建的事件不会再被使用了(可以调用 event_free 告知由 event_new 创建的事件已经无效了)。如果想在调试时避免耗尽内存,可以显式告知 libevent 这些事件不再被当作已分配的了

/**
 * When debugging mode is enabled, informs Libevent that an event should no
 * longer be considered as assigned. When debugging mode is not enabled, does
 * nothing.
 *
 * This function must only be called on a non-added event.
 *
 * @see event_enable_debug_mode()
 */
EVENT2_EXPORT_SYMBOL
void event_debug_unassign(struct event *);

没有启用调试的时候调用 event_debug_unassign 没有效果。

你可能感兴趣的:(Libevent,libevent)