Libevent

Libevent多线程,锁,条件变量

Posted by Liangjf on April 14, 2019

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这些结构体之后。因为这些结构体会使用到内存分配、日志记录、线程锁的。这三者的定制顺序是:内存分配->日志记录->线程锁。