libevent多线程
libevent默认是不开启多线程的,需要我们在配置编译的时候加上开启多线程的选项。
若没有开启多线程选项,libevent中没有使用到锁,条件变量等的。
若是单单开启了多线程的选项,如果没有调用了evthread_use_windows_threads()
或者evthread_use_pthreads()
或者调用 evthread_set_lock_callbacks
函数定制自己的多线程、锁、条件变量,也不会开启多线程功能。
锁结构
struct evthread_lock_callbacks {
int lock_api_version; //版本号,设置为宏 EVTHREAD_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);
};
libevent的锁类型支持以下的类型:supported_locktypes
- 普通锁。0
- 递归锁。#define EVTHREAD_LOCKTYPE_RECURSIVE 1(目前强制使用)
- 读写锁。#define EVTHREAD_LOCKTYPE_READWRITE 2(目前木有使用)
当定义了锁之类,就可以使用 alloc函数指针 来获得锁变量指针。
mode
的类型:
- #define EVTHREAD_WRITE 0x04:仅用于读写锁:为读操作请求或者释放锁
- #define EVTHREAD_READ 0x08:仅用于读写锁:为写操作请求或者释放锁
- #define EVTHREAD_TRY 0x10:仅用于锁定:仅在可以立刻锁定的时候才请求锁定
条件变量结构
struct evthread_condition_callbacks {
int condition_api_version; //#define EVTHREAD_CONDITION_API_VERSION 1
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);
};
- alloc_condition。申请并初始化一个条件变量。成功返回条件变量的指针,失败返回NULL。condtype的值为0,当使用
EVTHREAD_CONDITION_API_VERSION
这个版本号。 - signal_condition。唤醒一个条件变量,如果
broadcast
是1,那么就唤醒所有线程。,其他值就唤醒一个线程。成功返回0,失败返回-1。失败的话会锁住相关的条件变量。 - wait_condition。等待条件变量。
timeout
是等待的时间,注意注意注意。为NULL就一直等到有signal_condition
唤醒。 - free_condition。释放条件变量
使Libevent支持多线程
根据平台在event_base_new
函数之前调用以下函数:
- Windows。调用
evthread_use_windows_threads()
- pthreads。调用
evthread_use_pthreads()
看看 evthread_use_pthreads()
:
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
};
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;
}
先初始化锁结构和条件变量结构,然后再设置自定义锁,条件变量,获得线程id函数。。可以看到默认的递归锁。
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版本锁只支持递归锁和普通非递归锁,并不支持读写锁。
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_posix_lock
,可以确定是没有支持读写锁的了。
int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *cbs)
int evthread_set_condition_callbacks(const struct evthread_condition_callbacks *cbs)
选其中的一个看。
int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *cbs)
{
//获取现在全局的锁结构
struct evthread_lock_callbacks *target = evthread_get_lock_callbacks();
if (!cbs) {
if (target->alloc)
event_warnx("Trying to disable lock functions after "
"they have been set up will probaby not work.");
memset(target, 0, sizeof(evthread_lock_fns_));
return 0;
}
//如果已经有锁分配了。那么就设置为自定义的
if (target->alloc) {
if (target->lock_api_version == cbs->lock_api_version &&
target->supported_locktypes == cbs->supported_locktypes &&
target->alloc == cbs->alloc &&
target->free == cbs->free &&
target->lock == cbs->lock &&
target->unlock == cbs->unlock) {
return 0;
}
event_warnx("Can't change lock callbacks once they have been "
"initialized.");
return -1;
}
if (cbs->alloc && cbs->free && cbs->lock && cbs->unlock) {
memcpy(target, cbs, sizeof(evthread_lock_fns_));
return event_global_setup_locks_(1);
} else {
return -1;
}
}
从这里可以看出来,如果调用evthread_set_lock_callbacks
,就会把传进去的锁结构更新默认旧的锁结构,以后调用锁相关的接口就是使用自定义的了。
条件变量的处理也是一样的。
自定义的时机:
内存分配、日志、线程锁。这些定制都应该放在代码的最前面,即不能在使用Libevent的event、event_base这些结构体之后。因为这些结构体会使用到内存分配、日志记录、线程锁的。这三者的定制顺序是:内存分配->日志记录->线程锁。