草履虫也能看懂的Libevent中event_add_nolock_解析

草履虫也能看懂的Libevent中event_add_nolock_解析

与原先event_add的异同
首先他还是添加事件的实现函数。与event_add函数的工作方式几乎相同,

但有两点不同:
1)它需要我们拥有锁。
2)如果设置了tv_is_absolute标志,
我们将把tv视为绝对时间,而不是添加到当前时间的间隔

参数含义

int  event_add_nolock_(struct event *ev, const struct timeval *tv,
    int tv_is_absolute)

这个函数的定义包含了三个参数:ev、tv和tv_is_absolute。

ev是一个指向事件结构体的指针。该结构体包含了关于事件的一些信息,如回调函数、事件类型等。

tv是一个指向 timeval 结构体的指针,它表示事件的超时时间。timeval 结构体包含了秒数和微秒数,用于表示事件的相对或绝对超时时间。

tv_is_absolute是一个整数,用于判断 tv 参数表示的时间是相对时间还是绝对时间。如果 tv_is_absolute 为非零值,表示 tv 参数表示的时间是绝对时间,否则是相对时间。如果是绝对时间,ev 的 ev_timeout 成员将被直接设置为 tv 参数指向的时间值。如果是相对时间,则根据 common_timeout 的值进行不同的计算处理,并将结果赋值给 ev 的 ev_timeout 成员。

总结起来,ev 参数表示要添加到事件循环中的事件,tv 参数表示事件的超时时间,tv_is_absolute 参数表示 tv 参数的时间是相对时间还是绝对时间。

定义变量

	struct event_base *base = ev->ev_base;
	int res = 0;
	int notify = 0;

其中event_base是libevent库提供的事件处理框架的核心数据结构,用于管理和调度事件。它提供了事件循环、事件注册和事件触发的功能,可以用于开发高性能的事件驱动程序。event_base可以用来创建和管理事件,如网络套接字的读写事件、定时器事件、信号事件等。它可以处理异步IO、定时器和信号等事件,并提供了多种事件处理机制,如select、epoll、kqueue等。通过使用event_base,开发者可以方便地实现高效的事件驱动程序。

校验

确认自己能否访问

	EVENT_BASE_ASSERT_LOCKED(base);
	event_debug_assert_is_setup_(ev);

EVENT_BASE_ASSERT_LOCKED是一个事件基础定义,用于确保某个资源或对象被锁定。在编程中,锁定资源可以防止多个线程同时访问或修改它,从而保证数据的一致性和安全性。

当一个程序需要对共享资源进行操作时,可以使用锁来保护资源,以确保在同一时间只有一个线程可以访问该资源。EVENT_BASE_ASSERT_LOCKED事件可以用于验证在访问资源之前是否已经获得了对应的锁。
具体实现细节可能因编程语言和上下文而异,但通常在代码中会有一个锁对象或锁标记,通过调用相应的锁定方法(如acquire())来获取锁,然后在操作完成后释放锁(如release())。

event_debug_assert_is_setup_(ev) 的作用是在调试模式下,检测并断言一个事件(ev)是否已经被正确设置和初始化。它用于确保在操作或检查一个事件之前,该事件已经被正确创建和配置

debug模式下输出调试信息

	event_debug((
		 "event_add: event: %p (fd "EV_SOCK_FMT"), %s%s%s%scall %p",
		 ev,
		 EV_SOCK_ARG(ev->ev_fd),
		 ev->ev_events & EV_READ ? "EV_READ " : " ",
		 ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
		 ev->ev_events & EV_CLOSED ? "EV_CLOSED " : " ",
		 tv ? "EV_TIMEOUT " : " ",
		 ev->ev_callback));
		 
	EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));

这段代码包含两个部分:

**event_debug 宏:**它用于在调试模式下输出事件相关的调试消息。该宏使用格式化字符串,将一些事件属性和信息以文本形式打印出来。
event_add: event: %p (fd “EV_SOCK_FMT”), %s%s%s%scall %p:这是调试消息的固定前缀。
**%p:**将事件指针 ev 的内存地址显示出来。
(fd “EV_SOCK_FMT”):以格式化字符串的形式显示与事件相关联的文件描述符。
**%s%s%s%s:**根据事件的不同标志位(EV_READ、EV_WRITE、EV_CLOSED、EV_TIMEOUT),显示相应的文本字符串。
**call %p:**显示事件的回调函数的内存地址。
加粗样式EVUTIL_ASSERT 宏:它用于进行断言检查。该断言检查确保事件的标志位没有超出 EVLIST_ALL 定义的范围。如果断言条件不为真,将触发断言失败,并给出相应的错误消息。
这段代码的目的是在调试模式下,提供有关事件的详细信息和一些断言检查,以帮助开发人员调试和验证代码的正确性。

事件是否是就结束状态

	if (ev->ev_flags & EVLIST_FINALIZING) {
		/* XXXX debug */
		return (-1);
	}

通过这个判断条件,我们可以判断当前事件的标志位是否已经被设置为 EVLIST_FINALIZING,如果是,就执行 if 代码块中的逻辑,返回-1表示失败.
在事件驱动编程中,事件可以分为不同的状态,比如活跃(active)、挂起(suspended)、等待(waiting)等。
EVLIST_FINALIZING 标志位表示事件正在进行最终化处理,即事件即将被销毁或释放资源。

确认时间堆上还有位置

	/*
	 * prepare for timeout insertion further below, if we get a
	 * failure on any step, we should not change any state.
	 * 准备在下方进行超时插入,如果在任何步骤中发生失败,我们不应更改任何状态。
	 */
	if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
		if (min_heap_reserve_(&base->timeheap,
			1 + min_heap_size_(&base->timeheap)) == -1)
			return (-1);  /* ENOMEM == errno */
	}

检查了 tv 是否为非空,并且 ev->ev_flags 中的 EVLIST_TIMEOUT 标志位未被设置。如果条件满足,则执行下面的逻辑。

在该逻辑中,调用了 min_heap_reserve_ 函数,该函数用于为时间堆(timeheap)预留足够的空间以插入新的超时事件。这里通过传入 &base->timeheap 来操作基于堆的时间数据结构。

如果 min_heap_reserve_ 函数返回 -1,即表示预留空间失败,这通常是因为内存不足(ENOMEM)。在这种情况下,返回 -1,表示错误,并保持原有状态不做任何改变。

整体来说,这段代码的目的是在准备将超时事件插入时间堆之前进行一些必要的准备工作,如果任何准备步骤失败,应该保持原有状态不做任何改变,并返回 -1。

确认主线程当前空闲

	/* If the main thread is currently executing a signal event's
	 * callback, and we are not the main thread, then we want to wait
	 * until the callback is done before we mess with the event, or else
	 * we can race on ev_ncalls and ev_pncalls below. 
	 * 如果主线程当前正在执行一个信号事件的回调函数,
	 * 并且我们不是主线程,则我们希望在对事件进行任何操作之前等待回调函数执行完成,
	 * 否则我们可能会在下面的 ev_ncalls 和 ev_pncalls 上发生竞争。
	 * */
#ifndef EVENT__DISABLE_THREAD_SUPPORT
	if (base->current_event == event_to_event_callback(ev) &&
	    (ev->ev_events & EV_SIGNAL)
	    && !EVBASE_IN_THREAD(base)) {
		++base->current_event_waiters;
		EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);
	}
#endif

#ifndef 是一个条件编译指令,它的作用是判断某个宏是否未定义。如果该宏未定义,则执行 #ifndef 块中的代码;否则,跳过该块代码。
它检查了 base->current_event 是否等于 event_to_event_callback(ev),并且事件 ev 的事件类型包含 EV_SIGNAL。此外,还检查了当前线程是否不是主线程,即 !EVBASE_IN_THREAD(base)。

如果以上条件都满足,那么进入条件语句块内部。

在条件语句块内部,首先将 base->current_event_waiters 的值加一,表示当前等待的事件数量增加了一个。

然后,调用 EVTHREAD_COND_WAIT 函数,该函数用于等待条件变量 base->current_event_cond 的信号。在等待期间,会将 base->th_base_lock 作为锁来保护临界区,以防止竞争条件的发生。

整体而言,这段代码的目的是在主线程正在执行信号事件的回调函数时,如果当前线程不是主线程,那么在对事件进行任何操作之前,需要等待主线程的回调函数执行完成。这样可以避免在 ev_ncalls 和 ev_pncalls 上发生竞争条件。这段代码主要用于多线程环境下的事件处理。

添加

判断和添加事件到事件循环中

	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED|EV_SIGNAL)) &&
	    !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) {
		if (ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED))
			res = evmap_io_add_(base, ev->ev_fd, ev);
		else if (ev->ev_events & EV_SIGNAL)
			res = evmap_signal_add_(base, (int)ev->ev_fd, ev);
		if (res != -1)
			event_queue_insert_inserted(base, ev);
		if (res == 1) {
			/* evmap says we need to notify the main thread. */
			notify = 1;
			res = 0;
		}
	}

首先,判断事件的类型是否是EV_READ、EV_WRITE、EV_CLOSED或EV_SIGNAL中的一种,且事件的标志不包含EVLIST_INSERTED、EVLIST_ACTIVE或EVLIST_ACTIVE_LATER。这个判断条件用于过滤掉已经在事件循环中的事件或已经处于激活状态的事件。

如果满足上述条件,将根据事件的类型进行不同的处理:

如果事件类型是EV_READ、EV_WRITE或EV_CLOSED中的一种,将调用evmap_io_add_函数将事件添加到I/O事件映射表中。
如果事件类型是EV_SIGNAL,将调用evmap_signal_add_函数将事件添加到信号事件映射表中。
如果添加事件成功(返回值不等于-1),将调用event_queue_insert_inserted函数将事件插入到已插入事件队列中。

最后,如果添加事件的返回值是1,表示evmap需要通知主线程,将设置notify标志为1,并将res设置为0。

这段代码的作用是根据事件的类型将事件添加到相应的事件映射表中,并将事件插入到已插入事件队列中。如果需要通知主线程,则设置notify标志为1。

设置超时时间

校验前面事件是否添加成功

	/*
	 * we should change the timeout state only if the previous event
	 * addition succeeded.
	 * 只有在前面的事件添加成功的情况下,我们才应该更改超时状态
	 */
	if (res != -1 && tv != NULL) {
		struct timeval now;
		int common_timeout;
#ifdef USE_REINSERT_TIMEOUT
		int was_common;
		int old_timeout_idx;
#endif
		/*
		 * for persistent timeout events, we remember the
		 * timeout value and re-add the event.
		 *
		 * If tv_is_absolute, this was already set.
		 */
		if (ev->ev_closure == EV_CLOSURE_EVENT_PERSIST && !tv_is_absolute)
			ev->ev_io_timeout = *tv;

#ifndef USE_REINSERT_TIMEOUT
		if (ev->ev_flags & EVLIST_TIMEOUT) {
			event_queue_remove_timeout(base, ev);
		}
#endif
相对时间和绝对时间

在定时器中,相对时间和绝对时间是用来表示事件触发时间的两种不同方式。

相对时间是指相对于当前时间的时间间隔。例如,如果一个事件设置了一个相对时间为5秒,则表示该事件将在当前时间的基础上延迟5秒后触发。

绝对时间是指特定的日期和时间点,与当前时间无关。例如,如果设置一个事件的触发时间为明天的下午2点钟,那么这个时间就是绝对时间,不管当前时间是什么。

参数解析

USE_REINSERT_TIMEOUT是一个宏定义,用于控制在事件重复添加时是否启用超时机制。

当一个事件已经被添加到事件循环中,但是又被重新添加时,如果启用了USE_REINSERT_TIMEOUT,Libevent将会启动一个超时计时器,等待一段时间后再次尝试将事件添加到事件循环中。这个超时时间由REINSERT_TIMEOUT宏定义指定,默认为50毫秒。

启用超时机制可以避免在事件重复添加时出现死循环或者频繁地添加和删除事件,从而提高程序的性能和稳定性。但是如果事件的添加和删除操作比较频繁,启用超时机制可能会降低程序的响应速度。因此,在使用Libevent时需要根据实际情况来选择是否启用超时机制。

EV_CLOSURE_EVENT_PERSIST 是一个用于事件驱动编程中的宏定义。它用于指示一个事件是否是持久事件(persistent event)。持久事件是指在回调执行后需要继续保持活动状态的事件。

特定条件下触发事件

这段代码是一个条件语句,根据其中的条件做出相应的操作。让我们逐行分析:

if ((ev->ev_flags & EVLIST_ACTIVE) &&
	(ev->ev_res & EV_TIMEOUT)) {

这行代码首先检查事件 ev 是否处于活动状态(EVLIST_ACTIVE标志位被设置)并且是否由于超时而被触发(EV_TIMEOUT标志位被设置)。只有当这两个条件都满足时,才会执行下面的操作。

if (ev->ev_events & EV_SIGNAL) {
	/* See if we are just active executing
	 * this event in a loop
	 */
	if (ev->ev_ncalls && ev->ev_pncalls) {
		/* Abort loop */
		*ev->ev_pncalls = 0;
	}
}
ev_ncalls 和ev_pncalls

ev_ncalls 和 ev_pncalls 是用于控制事件循环中断的变量。

ev_ncalls 被用来记录事件循环执行回调函数的次数。当一个事件被触发时,对应的回调函数将会执行。ev_ncalls 用来记录回调函数执行的次数,每次执行回调函数时,该变量的值会自增。

ev_pncalls 是用来指向 ev_ncalls 变量的指针。它通常在循环执行回调函数时使用。在某些情况下,我们可能需要在循环执行中间中断回调函数的执行,这时就可以通过修改ev_pncalls指向的值来实现。将 ev_pncalls 指向的值设为0,就能够中断循环,让后续的回调函数不再执行。

通过使用 ev_ncalls 和 ev_pncalls,我们可以控制事件循环中回调函数的执行次数和循环中断,实现对事件处理的灵活控制。
如果事件

ev 是由信号触发的(EV_SIGNAL标志位被设置),则会进入这个条件语句块。它检查当前是否正在循环执行这个事件。为了判断是否正在循环执行,需要检查 ev_ncallsev_pncalls 变量是否非零。如果是,则会进行一个循环中断的操作,将 ev_pncalls 指向的值设置为0。

event_queue_remove_active(base, event_to_event_callback(ev));

无论上述条件是否满足,最终都会调用 event_queue_remove_active 函数,传递参数 baseevent_to_event_callback(ev),这个函数的作用是从活动事件列表中删除事件 ev

因此,这段代码的作用是检查并处理特定条件下的事件触发,包括信号事件循环中断等,并将触发的事件从活动事件列表中移除。

添加超时时间

		if (tv_is_absolute) {
			ev->ev_timeout = *tv;
		} else if (common_timeout) {
			struct timeval tmp = *tv;
			tmp.tv_usec &= MICROSECONDS_MASK;
			evutil_timeradd(&now, &tmp, &ev->ev_timeout);
			ev->ev_timeout.tv_usec |=
			    (tv->tv_usec & ~MICROSECONDS_MASK);
		} else {
			evutil_timeradd(&now, tv, &ev->ev_timeout);
		}

		event_debug((
			 "event_add: event %p, timeout in %d seconds %d useconds, call %p",
			 ev, (int)tv->tv_sec, (int)tv->tv_usec, ev->ev_callback));

首先,代码检查变量"tv_is_absolute"的值。如果为真,则将指针"tv"所指向的值赋给变量"ev->ev_timeout"。

如果"tv_is_absolute"为假,则检查变量"common_timeout"的值。如果为真,则创建一个临时变量"tmp",并将"tv"的值赋给它。接着,将"tmp.tv_usec"与"MICROSECONDS_MASK"进行按位与操作,将结果赋给"tmp.tv_usec"。然后,使用函数"evutil_timeradd"计算得到"now"和"tmp"的和,将结果赋给"ev->ev_timeout"。接着,将"tv->tv_usec"与"~MICROSECONDS_MASK"进行按位与操作,将结果赋给"ev->ev_timeout.tv_usec"。

如果"common_timeout"也为假,则使用函数"evutil_timeradd"计算得到"now"和"tv"的和,将结果赋给"ev->ev_timeout"。

在最后的代码行中,使用函数"event_debug"打印一条调试信息。该调试信息包括"ev"的地址,"tv"中的秒数和微秒数,以及"ev->ev_callback"的地址。

将事件插入到队列

#ifdef USE_REINSERT_TIMEOUT
		event_queue_reinsert_timeout(base, ev, was_common, common_timeout, old_timeout_idx);
#else
		event_queue_insert_timeout(base, ev);
#endif

这段代码是一个条件编译指令,用于根据预定义的宏USE_REINSERT_TIMEOUT来选择不同的函数调用。

首先,代码检查宏USE_REINSERT_TIMEOUT是否已经被定义。如果已经定义,则执行第一行代码:

event_queue_reinsert_timeout(base, ev, was_common, common_timeout, old_timeout_idx);
其中,event_queue_reinsert_timeout是一个函数,它会重新插入一个事件到事件队列中,同时更新事件的超时时间。这个函数通常用于在已有超时时间的事件重新设置超时时间时进行调用。

如果宏USE_REINSERT_TIMEOUT没有被定义,则执行第三行代码:

event_queue_insert_timeout(base, ev);
其中,event_queue_insert_timeout是一个函数,它会将事件按照超时时间的顺序插入到事件队列中。

这段代码的作用是根据不同的编译选项选择不同的函数来将事件插入或重新插入到事件队列中,以实现不同的功能或需求。

确认是否需通知主线程循环

if (common_timeout) {
			struct common_timeout_list *ctl =
			    get_common_timeout_list(base, &ev->ev_timeout);
			if (ev == TAILQ_FIRST(&ctl->events)) {
				common_timeout_schedule(ctl, &now, ev);
			}
		} else {
			struct event* top = NULL;
			/* See if the earliest timeout is now earlier than it
			 * was before: if so, we will need to tell the main
			 * thread to wake up earlier than it would otherwise.
			 * We double check the timeout of the top element to
			 * handle time distortions due to system suspension.
			 */
			if (min_heap_elt_is_top_(ev))
				notify = 1;
			else if ((top = min_heap_top_(&base->timeheap)) != NULL &&
					 evutil_timercmp(&top->ev_timeout, &now, <))
				notify = 1;
		}
	}

	/* if we are not in the right thread, we need to wake up the loop */
	if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
		evthread_notify_base(base);

	event_debug_note_add_(ev);

首先,代码检查变量common_timeout的值。如果为真,则表示该事件具有公共超时时间。接下来,代码调用函数get_common_timeout_list获取与当前事件超时时间相同的公共超时列表。通过判断事件是否为公共超时列表中的第一个事件,如果是,则调用函数common_timeout_schedule进行公共超时调度。此操作用于确保只有第一个事件的超时时间产生变化时才进行重新计算公共超时时间。

如果common_timeout为假,则代码继续执行else语句块。在else语句块中,代码检查当前事件是否为最早超时事件,如果是,则将变量notify设置为1。否则,代码检查时间堆中最早超时的事件,并将其与当前时间进行比较。如果时间堆中最早超时的事件早于当前时间,则将变量notify设置为1。

在if条件块之后,代码再次进行条件判断。如果res不为-1(表示事件添加成功)、notify为真(表示有需要通知的事件)且EVBASE_NEED_NOTIFY函数返回真(表示需要唤醒事件循环线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值