从一万英尺外看libevent(源码刨析)

从一万英尺外看libevent

img
温馨提示:阅读时间大概二十分钟

前言

Libevent是用于编写高速可移植非阻塞IO应用的库,其设计目标是:

  • 可移植性:使用libevent编写的程序应该可以在libevent支持的所有平台上工作。即使没有好的方式进行非阻塞IO,libevent也应该支持一般的方式,让程序可以在受限的环境中运行
  • 速度:libevent尝试使用每个平台上最高速的非阻塞IO实现,并且不引入太多的额外开销。
  • 可扩展性:libevent被设计为程序即使需要上万个活动套接字的时候也可以良好工作。
  • 方便:无论何时,最自然的使用libevent编写程序的方式应该是稳定的、可移植的。(Libevent should compile on Linux, *BSD, Mac OS X, Solaris, Windows, and more.)

libevent由下列组件构成:

  • evutil:用于抽象不同平台网络实现差异的通用功能。

  • event和event_base:libevent的核心,为各种平台特定的、基于事件的非阻塞IO后端提供抽象API,让程序可以知道套接字何时已经准备好,可以读或者写,并且处理基本的超时功能,检测OS信号。

  • bufferevent:为libevent基于事件的核心提供使用更方便的封装。除了通知程序套接字已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时IO已经真正发生。(bufferevent接口有多个后端,可以采用系统能够提供的更快的非阻塞IO方式,如Windows中的IOCP。)

  • evbuffer:在bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数。

  • evhttp:一个简单的HTTP客户端/服务器实现。

  • evdns:一个简单的DNS客户端/服务器实现。

  • evrpc:一个简单的RPC实现。


  • libevent API提供了一种机制,可以在文件描述符上发生特定事件或达到超时后执行回调函数。此外,libevent还支持由于信号或常规超时而产生的回调。

  • Libevent旨在取代事件驱动网络服务器中的事件循环。应用程序只需要调用event_dispatch(),然后动态地添加或删除事件,而不必更改事件循环。

  • Libevent还为缓冲网络IO提供了一个复杂的框架,支持套接字、过滤器、速率限制、SSL、零拷贝文件传输和IOCP。Libevent支持几种有用的协议,包括DNS、HTTP和一个最小的RPC框架。

在这里插入图片描述

创建libevent时,默认安装下列库:

  • libevent_core:所有核心的事件和缓冲功能,包含了所有的event_base、evbuffer、bufferevent和工具函数。

  • libevent_extra:定义了程序可能需要,也可能不需要的协议特定功能,包括HTTP、DNS和RPC。

  • libevent:这个库因为历史原因而存在,它包含libevent_core和libevent_extra的内容。不应该使用这个库,未来版本的libevent可能去掉这个库。

某些平台上可能安装下列库:

  • libevent_openssl:这个库为使用bufferevent和OpenSSL进行加密的通信提供支持。它独立于libevent_core,这样程序使用libevent时就不需要链接到OpenSSL,除非是进行加密通信。
  • ibevent_pthreads:添加基于pthread可移植线程库的线程和锁定实现。它独立于libevent_core,这样程序使用libevent时就不需要链接到pthread,除非是以多线程方式使用libevent

头文件

libevent公用头文件都安装在event2目录中,分为三类:

  • API头文件:定义libevent公用接口。这类头文件没有特定后缀。
  • 兼容头文件:为已废弃的函数提供兼容的头部包含定义。不应该使用这类头文件,除非是在移植使用较老版本libevent的程序时。
  • 结构头文件:这类头文件以相对不稳定的布局定义各种结构体。这些结构体中的一些是为了提供快速访问而暴露;一些是因为历史原因而暴露。直接依赖这类头文件中的任何结构体都会破坏程序对其他版本libevent的二进制兼容性,有时候是以非常难以调试的方式出现。这类头文件具有后缀“_struct.h”。

如何使用libevent

在这里插入图片描述

​ 使用libevent函数之前需要分配一个或者多个``event_base结构体,每个event_base`持有一个事件集合,可以检测哪个事件是激活(可读写)的。

​ 如果设置event_base使用锁,则可以安全地在多个线程中访问它。然而,其事件循环只能运行在一个线程中。如果需要用多个线程检测IO,则需要为每个线程使用一个event_base


每个event_base都有一种用于检测哪种事件已经就绪的“方法”.

  • select
  • poll
  • epoll
  • kqueue
  • devpool
  • evport
  • win32

​ 用户可以用环境变量禁止某些特定的后端。比如说,要禁止kqueue后端,可以设置EVENT_NOKQUEUE环境变量。如果要用编程的方法禁止后端,看关于event_config_avoid_method()的说明。

建立默认的event_base

event_base_new()函数分配并且返回一个新的具有默认设置的event_base。函数会检测环境变量,返回一个到event_base的指针。如果发生错误,则返回NULL。选择各种方法时,函数会选择OS支持的最快方法。

event_base_new(void)

event_base_new()函数声明在<event2/event.h>

struct event_base * event_base_new(void)
{
	struct event_base *base = NULL;
    //配置config
	struct event_config *cfg = event_config_new();
	if (cfg) {
		base = event_base_new_with_config(cfg);
		event_config_free(cfg);
	}
	return base;
}

创建复杂的event_base()

​ 要对取得什么类型的event_base有更多的控制,就需要使用event_config。event_config是一个容纳event_base配置信息的不透明结构体。需要event_base时,将event_config传递给event_base_new_with_config()。

event_config_new()
#define INT_MAX __INT_MAX__
//在 C/C++ 中用于表示 int 类型所能表示的最大值。它是一个编译器常量,值为 2147483647(0x7ffffffff)
 

struct event_config * event_config_new(void)
{
	struct event_config *cfg = mm_calloc(1, sizeof(*cfg));

	if (cfg == NULL)
		return (NULL);

	TAILQ_INIT(&cfg->entries);
	cfg->max_dispatch_interval.tv_sec = -1;
	cfg->max_dispatch_callbacks = INT_MAX;
	cfg->limit_callbacks_after_prio = 1;

	return (cfg);
}

/*
 * Tail queue functions.
 * 尾队列的头结点初始化为空队列。
 */
#define	TAILQ_INIT(head) do {						\
	(head)->tqh_first = NULL;					\
	(head)->tqh_last = &(head)->tqh_first;				\
} while (/*CONSTCOND*/0)

要使用这些函数分配event_base,先调用event_config_new()分配一个event_config。然后,对event_config调用其它函数,设置所需要的event_base特征。最后,调用event_base_new_with_config()获取新的event_base。完成工作后,使用event_config_free()释放event_config


event_base_new_with_config

这个函数在new_event_base被调用,传入的参数是event_config,根据cfg的内容来配置event_base

struct event_base * event_base_new_with_config(const struct event_config *cfg)
{
	int i;
	struct event_base *base;
	int should_check_environment;

#ifndef EVENT__DISABLE_DEBUG_MODE
		event_debug_mode_too_late = 1;
#endif

	if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) { //安全分配内存
		event_warn("%s: calloc", __func__);
		return NULL;
	}

	if (cfg)
		base->flags = cfg->flags;

	should_check_environment =
	    !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));

	{
		struct timeval tmp;
		int precise_time =
		    cfg && (cfg->flags & EVENT_BASE_FLAG_PRECISE_TIMER);
		int flags;
		if (should_check_environment && !precise_time) {
			precise_time = evutil_getenv_("EVENT_PRECISE_TIMER") != NULL;
			if (precise_time) {
				base->flags |= EVENT_BASE_FLAG_PRECISE_TIMER;
			}
		}
		flags = precise_time ? EV_MONOT_PRECISE : 0;
		evutil_configure_monotonic_time_(&base->monotonic_timer, flags);

		gettime(base, &tmp);
	}

	min_heap_ctor_(&base->timeheap);

	base->sig.ev_signal_pair[0] = -1;
	base->sig.ev_signal_pair[1] = -1;
	base->th_notify_fd[0] = -1;
	base->th_notify_fd[1] = -1;

	TAILQ_INIT(&base->active_later_queue);

	evmap_io_initmap_(&base->io);
	evmap_signal_initmap_(&base->sigmap);
	event_changelist_init_(&base->changelist);

	base->evbase = NULL;

	if (cfg) {
		memcpy(&base->max_dispatch_time,
		    &cfg->max_dispatch_interval, sizeof(struct timeval));
		base->limit_callbacks_after_prio =
		    cfg->limit_callbacks_after_prio;
	} else {
		base->max_dispatch_time.tv_sec = -1;
		base->limit_callbacks_after_prio = 1;
	}
	if (cfg && cfg->max_dispatch_callbacks >= 0) {
		base->max_dispatch_callbacks = cfg->max_dispatch_callbacks;
	} else {
		base->max_dispatch_callbacks = INT_MAX;
	}
	if (base->max_dispatch_callbacks == INT_MAX &&
	    base->max_dispatch_time.tv_sec == -1)
		base->limit_callbacks_after_prio = INT_MAX;

	for (i = 0; eventops[i] && !base->evbase; i++) {
		if (cfg != NULL) {
			/* determine if this backend should be avoided */
			if (event_config_is_avoided_method(cfg,
				eventops[i]->name))
				continue;
			if ((eventops[i]->features & cfg->require_features)
			    != cfg->require_features)
				continue;
		}

		/* also obey the environment variables */
		if (should_check_environment &&
		    event_is_method_disabled(eventops[i]->name))
			continue;

		base->evsel = eventops[i];

		base->evbase = base->evsel->init(base);
	}

	if (base->evbase == NULL) {
		event_warnx("%s: no event mechanism available",
		    __func__);
		base->evsel = NULL;
		event_base_free(base);
		return NULL;
	}

	if (evutil_getenv_("EVENT_SHOW_METHOD"))
		event_msgx("libevent using: %s", base->evsel->name);

	/* allocate a single active event queue */
	if (event_base_priority_init(base, 1) < 0) {
		event_base_free(base);
		return NULL;
	}

	/* prepare for threading */

#if !defined(EVENT__DISABLE_THREAD_SUPPORT) && !defined(EVENT__DISABLE_DEBUG_MODE)
	event_debug_created_threadable_ctx_ = 1;
#endif

#ifndef EVENT__DISABLE_THREAD_SUPPORT
	if (EVTHREAD_LOCKING_ENABLED() &&
	    (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
		int r;
		EVTHREAD_ALLOC_LOCK(base->th_base_lock, 0);
		EVTHREAD_ALLOC_COND(base->current_event_cond);
		r = evthread_make_base_notifiable(base);
		if (r<0) {
			event_warnx("%s: Unable to make base notifiable.", __func__);
			event_base_free(base);
			return NULL;
		}
	}
#endif

#ifdef _WIN32
	if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP))
		event_base_start_iocp_(base, cfg->n_cpus_hint);
#endif

	return (base);
}



event_config_free
//用来释放config
void event_config_free(struct event_config *cfg)
{
	struct event_config_entry *entry;

	while ((entry = TAILQ_FIRST(&cfg->entries)) != NULL) {
		TAILQ_REMOVE(&cfg->entries, entry, next);
		event_config_entry_free(entry);
	}
	mm_free(cfg);
}


event_config_avoid_method()
int event_config_avoid_method(struct event_config *cfg, const char *method)
{
	struct event_config_entry *entry = mm_malloc(sizeof(*entry));
	if (entry == NULL)
		return (-1);

	if ((entry->avoid_method = mm_strdup(method)) == NULL) {
		mm_free(entry);
		return (-1);
	}

	TAILQ_INSERT_TAIL(&cfg->entries, entry, next);

	return (0);
}

​ 调用event_config_avoid_method()可以通过名字让libevent避免使用特定的可用后端。调用event_config_require_feature()让libevent不使用不能提供所有指定特征的后端。调用event_config_set_flag()让libevent在创建event_base时设置一个或者多个将在下面介绍的运行时标志。


event_config_require_feature()
int event_config_require_features(struct event_config *cfg,int features)
{
	if (!cfg)
		return (-1);
	cfg->require_features = features;
	return (0);
}
  • EV_FEATURE_ET:要求支持边沿触发的后端

  • EV_FEATURE_O1:要求添加、删除单个事件,或者确定哪个事件激活的操作是O(1)复杂度的后端

  • EV_FEATURE_FDS:要求支持任意文件描述符,而不仅仅是套接字的后端


event_config_set_flag
int event_config_set_flag(struct event_config *cfg, int flag)
{
	if (!cfg)
		return -1;
	cfg->flags |= flag;
	return 0;
}
  • EVENT_BASE_FLAG_NOLOCK:不要为event_base分配锁。设置这个选项可以为event_base节省一点用于锁定和解锁的时间,但是让在多个线程中访问event_base成为不安全的。

  • EVENT_BASE_FLAG_IGNORE_ENV:选择使用的后端时,不要检测EVENT_*环境变量。使用这个标志需要三思:这会让用户更难调试你的程序与libevent的交互。

  • EVENT_BASE_FLAG_STARTUP_IOCP:仅用于Windows,让libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用。

  • EVENT_BASE_FLAG_NO_CACHE_TIME:不是在事件循环每次准备执行超时回调时检测当前时间,而是在每次超时回调后进行检测。注意:这会消耗更多的CPU时间。

  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:告诉libevent,如果决定使用epoll后端,可以安全地使用更快的基于changelist的后端。epoll-changelist后端可以在后端的分发函数调用之间,同样的fd多次修改其状态的情况下,避免不必要的系统调用。但是如果传递任何使用dup()或者其变体克隆的fd给libevent,epoll-changelist后端会触发一个内核bug,导致不正确的结果。在不使用epoll后端的情况下,这个标志是没有效果的。也可以通过设置EVENT_EPOLL_USE_CHANGELIST环境变量来打开epoll-changelist选项。

上述操作event_config的函数都在成功时返回0,失败时返回-1。

注意

​ 设置event_config,请求OS不能提供的后端是很容易的。比如说,对于libevent 2.0.1-alpha,在Windows中是没有O(1)后端的;在Linux中也没有同时提供EV_FEATURE_FDS和EV_FEATURE_O1特征的后端。如果创建了libevent不能满足的配置,event_base_new_with_config()会返回NULL。

event_config_set_num_cpus_hint()

int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus)
{
	if (!cfg)
		return (-1);
	cfg->n_cpus_hint = cpus;
	return (0);
}

​ 这个函数当前仅在Windows上使用IOCP时有用,虽然将来可能在其他平台上有用。这个函数告诉event_config在生成多线程event_base的时候,应该试图使用给定数目的CPU。注意这仅仅是一个提示:event_base使用的CPU可能比你选择的要少。

这些函数和类型在<event2/event.h>中声明。

EVENT_BASE_FLAG_IGNORE_ENV标志首次出现在2.0.2-alpha版本。event_config_set_num_cpus_hint()函数是2.0.7-rc版本新引入的。检查event_base的后端方法

event_get_supported_methods
const char ** event_get_supported_methods(void)
{
	static const char **methods = NULL;
	const struct eventop **method;
	const char **tmp;
	int i = 0, k;

	/* count all methods */
	for (method = &eventops[0]; *method != NULL; ++method) {
		++i;
	}

	/* allocate one more than we need for the NULL pointer */
	tmp = mm_calloc((i + 1), sizeof(char *));
	if (tmp == NULL)
		return (NULL);

	/* populate the array with the supported methods */
	for (k = 0, i = 0; eventops[k] != NULL; ++k) {
		tmp[i++] = eventops[k]->name;
	}
	tmp[i] = NULL;

	if (methods != NULL)
		mm_free((char**)methods);

	methods = tmp;

	return (methods);
}

event_get_supported_methods()函数返回一个指针,指向libevent支持的方法名字数组。这个数组的最后一个元素是NULL。

注意

这个函数返回libevent被编译以支持的方法列表。然而libevent运行的时候,操作系统可能不能支持所有方法。比如说,可能OS X版本中的kqueue的bug太多,无法使用。

event_base_get_method
const char * event_base_get_method(const struct event_base *base)
{
	EVUTIL_ASSERT(base);//检查给定的条件 cond 是否满足
	return (base->evsel->name);
}
#define EVUTIL_ASSERT(cond)						\
	do {								\
		if (EVUTIL_UNLIKELY(!(cond))) {				\
			event_errx(EVENT_ERR_ABORT_,			\
			    "%s:%d: Assertion %s failed in %s",		\
			    __FILE__,__LINE__,#cond,__func__);		\
			/* In case a user-supplied handler tries to */	\
			/* return control to us, log and abort here. */	\
			(void)fprintf(stderr,				\
			    "%s:%d: Assertion %s failed in %s",		\
			    __FILE__,__LINE__,#cond,__func__);		\
			abort();					\
		}							\
	} while (0)
void event_errx(int eval, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	event_logv_(EVENT_LOG_ERR, NULL, fmt, ap);
	va_end(ap);
	event_exit(eval);
}

释放event_base

​ 使用完event_base之后,使用event_base_free()进行释放。

event_base_free()
void
event_base_free(struct event_base *base)
{
	event_base_free_(base, 1);
}

参数 base 是指向需要释放的 event_base 结构的指针,run_finalizers 是一个标志,用于指示是否需要运行终结器。

static void
event_base_free_(struct event_base *base, int run_finalizers)
{
	int i, n_deleted=0;
	struct event *ev;
	/* XXXX grab the lock? If there is contention when one thread frees
	 * the base, then the contending thread will be very sad soon. */
	/* 这段注释表明,在一个线程释放资源(通常指的是一个共享的基础对象或结构)时,可能需要获取一个锁(lock)来防止其他线程同时	访问该资源。如果不加锁,而另一个线程尝试访问同一个资源,就会发生竞争(contention)。竞争会导致程序不正确的行为,甚至崩	溃。*/
    
    
	/* event_base_free(NULL) is how to free the current_base if we
	 * made it with event_init and forgot to hold a reference to it. */
   	/*这段注释解释了如何释放当前的基础对象。如果您使用 event_init 函数创建了一个基础对象,但忘记了保存对它的引用,那么可以通		过调用 event_base_free(NULL) 来释放它。这表明 event_base_free 函数允许一个 NULL 参数,这种情况下,它将释放当前	   上下文中的基础对象。*/
	if (base == NULL && current_base)
		base = current_base;
	/* Don't actually free NULL. */
	if (base == NULL) {
		event_warnx("%s: no base to free", __func__);
		return;
	}
	/* XXX(niels) - check for internal events first */

#ifdef _WIN32
	event_base_stop_iocp_(base);
#endif

	/* threading fds if we have them */
	if (base->th_notify_fd[0] != -1) {
		event_del(&base->th_notify);
		EVUTIL_CLOSESOCKET(base->th_notify_fd[0]);
		if (base->th_notify_fd[1] != -1)
			EVUTIL_CLOSESOCKET(base->th_notify_fd[1]);
		base->th_notify_fd[0] = -1;
		base->th_notify_fd[1] = -1;
		event_debug_unassign(&base->th_notify);
	}

	/* Delete all non-internal events. */
	evmap_delete_all_(base);

	while ((ev = min_heap_top_(&base->timeheap)) != NULL) {
		event_del(ev);
		++n_deleted;
	}
	for (i = 0; i < base->n_common_timeouts; ++i) {
		struct common_timeout_list *ctl =
		    base->common_timeout_queues[i];
		event_del(&ctl->timeout_event); /* Internal; doesn't count */
		event_debug_unassign(&ctl->timeout_event);
		for (ev = TAILQ_FIRST(&ctl->events); ev; ) {
			struct event *next = TAILQ_NEXT(ev,
			    ev_timeout_pos.ev_next_with_common_timeout);
			if (!(ev->ev_flags & EVLIST_INTERNAL)) {
				event_del(ev);
				++n_deleted;
			}
			ev = next;
		}
		mm_free(ctl);
	}
	if (base->common_timeout_queues)
		mm_free(base->common_timeout_queues);

	for (;;) {
		/* For finalizers we can register yet another finalizer out from
		 * finalizer, and iff finalizer will be in active_later_queue we can
		 * add finalizer to activequeues, and we will have events in
		 * activequeues after this function returns, which is not what we want
		 * (we even have an assertion for this).
		 *
		 * A simple case is bufferevent with underlying (i.e. filters).
		 */
		int i = event_base_free_queues_(base, run_finalizers);
		event_debug(("%s: %d events freed", __func__, i));
		if (!i) {
			break;
		}
		n_deleted += i;
	}

	if (n_deleted)
		event_debug(("%s: %d events were still set in base",
			__func__, n_deleted));

	while (LIST_FIRST(&base->once_events)) {
		struct event_once *eonce = LIST_FIRST(&base->once_events);
		LIST_REMOVE(eonce, next_once);
		mm_free(eonce);
	}

	if (base->evsel != NULL && base->evsel->dealloc != NULL)
		base->evsel->dealloc(base);

	for (i = 0; i < base->nactivequeues; ++i)
		EVUTIL_ASSERT(TAILQ_EMPTY(&base->activequeues[i]));

	EVUTIL_ASSERT(min_heap_empty_(&base->timeheap));
	min_heap_dtor_(&base->timeheap);

	mm_free(base->activequeues);

	evmap_io_clear_(&base->io);
	evmap_signal_clear_(&base->sigmap);
	event_changelist_freemem_(&base->changelist);

	EVTHREAD_FREE_LOCK(base->th_base_lock, 0);
	EVTHREAD_FREE_COND(base->current_event_cond);

	/* If we're freeing current_base, there won't be a current_base. */
	if (base == current_base)
		current_base = NULL;
	mm_free(base);
}

主要代码逻辑
/* Global state; deprecated */
//全局的event_base current_base(event_global_current_base)
EVENT2_EXPORT_SYMBOL
struct event_base *event_global_current_base_ = NULL;
#define current_base event_global_current_base_

  1. 处理 NULL 基础对象: 如果 baseNULL 并且 current_base 存在,那么将 base 设置为 current_base

    if (base == NULL && current_base)
        base = current_base;d
    if (base == NULL) {
        event_warnx("%s: no base to free", __func__);
        return;
    }
    
  2. 停止 Windows 特定的 IOCP 处理(仅在 Windows 平台上有效):

    #ifdef _WIN32
    event_base_stop_iocp_(base);//此处不做讨论
    #endif
    
  3. 处理线程通知文件描述符: 删除线程通知事件并关闭文件描述符。

    if (base->th_notify_fd[0] != -1) {
        event_del(&base->th_notify);
        EVUTIL_CLOSESOCKET(base->th_notify_fd[0]);
        if (base->th_notify_fd[1] != -1)
            EVUTIL_CLOSESOCKET(base->th_notify_fd[1]);
        base->th_notify_fd[0] = -1;
        base->th_notify_fd[1] = -1;
        event_debug_unassign(&base->th_notify);
    }
    
  4. 删除所有非内部事件: 删除时间堆中的所有事件,并释放公共超时队列中的事件。

    evmap_delete_all_(base);
    while ((ev = min_heap_top_(&base->timeheap)) != NULL) {
        event_del(ev);
        ++n_deleted;
    }
    for (i = 0; i < base->n_common_timeouts; ++i) {
        struct common_timeout_list *ctl = base->common_timeout_queues[i];
        event_del(&ctl->timeout_event); // Internal; doesn't count
        event_debug_unassign(&ctl->timeout_event);
        for (ev = TAILQ_FIRST(&ctl->events); ev; ) {
            struct event *next = TAILQ_NEXT(ev, ev_timeout_pos.ev_next_with_common_timeout);
            if (!(ev->ev_flags & EVLIST_INTERNAL)) {
                event_del(ev);
                ++n_deleted;
            }
            ev = next;
        }
        mm_free(ctl);
    }
    if (base->common_timeout_queues)
        mm_free(base->common_timeout_queues);
    
  5. 释放一次性事件: 释放 once_events 列表中的事件。

    while (LIST_FIRST(&base->once_events)) {
        struct event_once *eonce = LIST_FIRST(&base->once_events);
        LIST_REMOVE(eonce, next_once);
        mm_free(eonce);
    }
    
  6. 释放选择器和锁: 释放事件选择器和相关的锁与条件变量。

    if (base->evsel != NULL && base->evsel->dealloc != NULL)
        base->evsel->dealloc(base);
    
    for (i = 0; i < base->nactivequeues; ++i)
        EVUTIL_ASSERT(TAILQ_EMPTY(&base->activequeues[i]));
    
    EVUTIL_ASSERT(min_heap_empty_(&base->timeheap));
    min_heap_dtor_(&base->timeheap);
    
    mm_free(base->activequeues);
    
    evmap_io_clear_(&base->io);
    evmap_signal_clear_(&base->sigmap);
    event_changelist_freemem_(&base->changelist);
    
    EVTHREAD_FREE_LOCK(base->th_base_lock, 0);
    EVTHREAD_FREE_COND(base->current_event_cond);
    
  7. 释放基础对象: 如果正在释放 current_base,则将其设置为 NULL,并最终释放 base

    if (base == current_base)
        current_base = NULL;
    mm_free(base);
    
总结

这个函数通过清理和释放所有相关资源和内存来销毁一个 event_base 对象。注释中提到的竞争条件提醒开发者在多线程环境中小心处理共享资源,确保程序的稳定性和正确性。

设置event_base的优先级

​ libevent支持为事件设置多个优先级。然而,event_base默认只支持单个优先级。可以调用event_base_priority_init()设置event_base的优先级数目。

int event_base_priority_init(struct event_base *base, int npriorities)

​ 成功时这个函数返回0,失败时返回-1。base是要修改的event_base,n_priorities是要支持的优先级数目,这个数目至少是1。每个新的事件可用的优先级将从0(最高)到n_priorities-1(最低)。

​ 常量EVENT_MAX_PRIORITIES表示n_priorities的上限。调用这个函数时为n_priorities给出更大的值是错误的。

注意

必须在任何事件激活之前调用这个函数,最好在创建event_base后立刻调用。

示例

关于示例,请看event_priority_set的文档·。

默认情况下,与event_base相关联的事件将被初始化为具有优先级n_priorities / 2event_base_priority_init()函数定义在<event2/event.h>中,从libevent 1.0版就可用了。

*这个宏用于获取event_base*的锁(如果有support)

/** Lock an event_base, if it is set up for locking.  Acquires the lock
    in the base structure whose field is named 'lockvar'. */
#define EVBASE_ACQUIRE_LOCK(base, lockvar) do {				\
		EVLOCK_LOCK((base)->lockvar, 0);			\
	} while (0)

#define N_ACTIVE_CALLBACKS(base)					\
	((base)->event_count_active) //最大事件数量

/** Largest number of priorities that Libevent can support. */
#define EVENT_MAX_PRIORITIES 256
/**

 Set the number of different event priorities

 By default Libevent schedules all active events with the same priority.

 However, some time it is desirable to process some events with a higher

 priority than others.  For that reason, Libevent supports strict priority

 queues.  Active events with a lower priority are always processed before

 events with a higher priority.

 The number of different priorities can be set initially with the

 event_base_priority_init() function.  This function should be called

 before the first call to event_base_dispatch().  The

 event_priority_set() function can be used to assign a priority to an

 event.  By default, Libevent assigns the middle priority to all events

 unless their priority is explicitly set.

 Note that urgent-priority events can starve less-urgent events: after

 running all urgent-priority callbacks, Libevent checks for more urgent

 events again, before running less-urgent events.  Less-urgent events

 will not have their callbacks run until there are no events more urgent

 than them that want to be active.

 @param eb the event_base structure returned by event_base_new()

 @param npriorities the maximum number of priorities

 @return 0 if successful, or -1 if an error occurred

 @see event_priority_set()

 */
int event_base_priority_init(struct event_base *base, int npriorities)
{
	int i, r;
	r = -1; //用于指示返回值的状态	
	//在多线程环境中,需要获取锁以确保对 base 的修改是线程安全的。
	EVBASE_ACQUIRE_LOCK(base, th_base_lock);
    //展开类似 EVLOCK_LOCK((base)->th_base_lock, 0);

	//参数和状态检查
	if (N_ACTIVE_CALLBACKS(base) || npriorities < 1
	    || npriorities >= EVENT_MAX_PRIORITIES)
		goto err;
	//检查是否需要重新分配队列	
	if (npriorities == base->nactivequeues)
		goto ok;
	
	if (base->nactivequeues) {
		mm_free(base->activequeues);
		base->nactivequeues = 0;
	}

	/* Allocate our priority queues */
    //分配新的优先级队列
	base->activequeues = (struct evcallback_list *)
	  mm_calloc(npriorities, sizeof(struct evcallback_list));
	if (base->activequeues == NULL) {
		event_warn("%s: calloc", __func__);
		goto err;
	}
	base->nactivequeues = npriorities;

	for (i = 0; i < base->nactivequeues; ++i) {
		TAILQ_INIT(&base->activequeues[i]);
	}

ok:
	r = 0;
err:
	EVBASE_RELEASE_LOCK(base, th_base_lock);
	return (r);
}
总结

这个函数的主要目的是为事件基础结构 base 初始化或重新初始化优先级队列。它通过以下步骤实现:

  1. 获取锁以确保线程安全。
  2. 检查输入参数和当前状态。
  3. 如果需要,释放现有的优先级队列。
  4. 分配并初始化新的优先级队列。
  5. 设置返回状态并释放锁。

通过这些步骤,确保 event_base 可以正确管理不同优先级的事件。

libevent的内存分配与管理

内存分配的相关宏定义

#define mm_malloc(sz) 			event_mm_malloc_(sz)
#define mm_calloc(count, size) 	event_mm_calloc_((count), (size))
#define mm_strdup(s) 			event_mm_strdup_(s)
#define mm_realloc(p, sz) 		event_mm_realloc_((p), (sz))
#define mm_free(p) 				event_mm_free_(p)
//#ifdef EVENT__DISABLE_MM_REPLACEMENT 下
#define mm_malloc(sz) malloc(sz)
#define mm_calloc(n, sz) calloc((n), (sz))
#define mm_strdup(s) strdup(s)
#define mm_realloc(p, sz) realloc((p), (sz))
#define mm_free(p) free(p)

static void *(*mm_malloc_fn_)(size_t sz) = NULL;
static void *(*mm_realloc_fn_)(void *p, size_t sz) = NULL;
static void (*mm_free_fn_)(void *p) = NULL;

mm_realloc

void *  event_mm_realloc_(void *ptr, size_t sz)
{
	if (mm_realloc_fn_)
		return mm_realloc_fn_(ptr, sz);
	else
		return realloc(ptr, sz);
}

mm_strdup

char * event_mm_strdup_(const char *str)
{
	if (!str) {
		errno = EINVAL;
		return NULL;
	}

	if (mm_malloc_fn_) {
		size_t ln = strlen(str);
		void *p = NULL;
		if (ln == EV_SIZE_MAX)
			goto error;
		p = mm_malloc_fn_(ln+1);
		if (p)
			return memcpy(p, str, ln+1);
	} else
        //不同平台下的函数不一样
#ifdef _WIN32
		return _strdup(str);
#else
		return strdup(str);
#endif

error:
	errno = ENOMEM;
	return NULL;
}
 

mm_malloc

void * event_mm_malloc_(size_t sz)
{
	if (sz == 0)
		return NULL;

	if (mm_malloc_fn_)
		return mm_malloc_fn_(sz);
	else
		return malloc(sz);
}

mm_free()

#define mm_free(p) event_mm_free_(p)

static void (*mm_free_fn_)(void *p) = NULL;//指向释放内存的函数
void event_mm_free_(void *ptr)
{
	if (mm_free_fn_)
		mm_free_fn_(ptr);
	else
		free(ptr);
}

mm_calloc()

 

#ifndef EVENT__DISABLE_MM_REPLACEMENT
#define mm_malloc(sz) event_mm_malloc_(sz)
#define mm_calloc(count, size) event_mm_calloc_((count), (size))
#define mm_strdup(s) event_mm_strdup_(s)
#define mm_realloc(p, sz) event_mm_realloc_((p), (sz))
#define mm_free(p) event_mm_free_(p)
#else
#define mm_malloc(sz) malloc(sz)
#define mm_calloc(n, sz) calloc((n), (sz))
#define mm_strdup(s) strdup(s)
#define mm_realloc(p, sz) realloc((p), (sz))
#define mm_free(p) free(p)
#endif //EVENT__DISABLE_MM_REPLACEMENT

static void *(*mm_malloc_fn_)(size_t sz) = NULL;//定义一个接受参数为seize_t类型的函数指针mm_malloc_fn_
void * event_mm_calloc_(size_t count, size_t size)
{
	if (count == 0 || size == 0)
		return NULL;

	if (mm_malloc_fn_) { 
        //非空
		size_t sz = count * size;
		void *p = NULL;
		if (count > EV_SIZE_MAX / size)
			goto error;
		p = mm_malloc_fn_(sz);
		if (p)
			return memset(p, 0, sz);
	} else {
        //为空
		void *p = calloc(count, size);
#ifdef _WIN32
		/* Windows calloc doesn't reliably set ENOMEM */
		if (p == NULL)
			goto error;
#endif
		return p;
	}

error:
	errno = ENOMEM;
	return NULL;
}

libevent的一些重要的结构体

event_config_entry结构体

libevent 中的事件配置项。

通过使用 event_config_entry 结构体,可以在 libevent 中定义和管理多个事件配置项,并按照链表的方式进行链接和访问。每个配置项都包含一个要避免使用的网络通信方法。

struct event_config_entry {
	TAILQ_ENTRY(event_config_entry) next;

	const char *avoid_method;//要避免使用的网络通信方法。
};
//通过使用 TAILQ_ENTRY 宏,可以为指定的数据类型创建一个双向链表的入口和出口结构体,方便在链表中进行插入、删除和遍历等操作。
#define	_TAILQ_ENTRY(type, qual)					\
struct {								\
	qual type *tqe_next;		/* next element */		\
	qual type *qual *tqe_prev;	/* address of previous next element */\
}
#define TAILQ_ENTRY(type)	_TAILQ_ENTRY(struct type,)

qual 用于指定链表结构体成员的修饰符(它可以是 constvolatile 或其他限定符。)

  • tqe_next:指向链表中下一个元素的指针。
  • tqe_prev:指向链表中上一个元素的指针的地址。这里使用了一个指向指针的指针,即二级指针,用于在删除元素时修改前一个元素的 tqe_next 指针。

event_base结构体

struct event_base {
	//一个指向特定于后端数据的指针,用于描述这个event_base端。
	const struct eventop *evsel;
	//一个指向特定于后端数据的指针,用于指向底层事件驱动后端的实现。
	void *evbase;

	/** List of changes to tell backend about at next dispatch.  Only used
	 * by the O(1) backends. */
    
    //一个结构体,用于描述在下次调度时需要通知后端更改的事件
	struct event_changelist changelist;
	
	/** Function pointers used to describe the backend that this event_base
	 * uses for signals */
    
    //一个指向特定于信号处理后端数据的指针,用于描述后端event_base*用于信号处理。
	const struct eventop *evsigsel;
    
	/** Data to implement the common signal handler code. */
    //一个结构体,用于实现信号处理通用代码的数据。
	struct evsig_info sig;

	/** Number of virtual events */
    //用于表示当前虚拟事件数量。
	int virtual_event_count;
    
	/** Maximum number of virtual events active */
    //用于表示最大虚拟事件数量。
	int virtual_event_count_max;
    
	/** Number of total events added to this event_base */
    //用于表示已添加到event_base的事件数量。
	int event_count;
    
	/** Maximum number of total events added to this event_base */
    //用于表示最大已添加到event_base的事件数量。
	int event_count_max;
    
	/** Number of total events active in this event_base */
    //用于表示当前活动事件数量。
	int event_count_active;
    
	/** Maximum number of total events active in this event_base */
    //用于表示最大活动事件数量。
	int event_count_active_max;

	/** Set if we should terminate the loop once we're done processing
	 * events. */
    //用于表示是否应该在处理完事件后终止循环。
	int event_gotterm;
    
	/** Set if we should terminate the loop immediately */
	
    //用于表示是否应该立即终止循环。
	int event_break;
    
	/** Set if we should start a new instance of the loop immediately. */
    //用于表示是否应该在处理完事件后继续执行循环。
	int event_continue;

	/** The currently running priority of events */
    //用于表示当前正在运行的事件优先级。
	int event_running_priority;

	/** Set if we're running the event_base_loop function, to prevent
	 * reentrant invocation. */
    //用于表示是否正在运行event_base_loop函数,以防止重入调用。
	int running_loop;

	/** Set to the number of deferred_cbs we've made 'active' in the
	 * loop.  This is a hack to prevent starvation; it would be smarter
	 * to just use event_config_set_max_dispatch_interval's max_callbacks
	 * feature */
    //用于表示已 deferred_cbs 数量
	int n_deferreds_queued;

	/* Active event management. */
	/** An array of nactivequeues queues for active event_callbacks (ones
	 * that have triggered, and whose callbacks need to be called).  Low
	 * priority numbers are more important, and stall higher ones.
	 */
    //用于存储活动事件队列。
	struct evcallback_list *activequeues;
	/** The length of the activequeues array */
	int nactivequeues;//用于表示活动事件队列的数量。
	/** A list of event_callbacks that should become active the next time
	 * we process events, but not this time. */
    //于存储应该在下次处理事件时激活的事件。
	struct evcallback_list active_later_queue;

	/* common timeout logic */

	/** An array of common_timeout_list* for all of the common timeout
	 * values we know. */
    //用于存储所有已知的时间outs。
	struct common_timeout_list **common_timeout_queues;

    /** The number of entries used in common_timeout_queues */
    //用于表示已使用的时间outs数量。
	int n_common_timeouts;
    
	/** The total size of common_timeout_queues. */
    //用于表示已分配的时间outs数量。
	int n_common_timeouts_allocated;

	/** Mapping from file descriptors to enabled (added) events */
    //用于存储文件描述符到已添加事件的映射。
	struct event_io_map io;

	/** Mapping from signal numbers to enabled (added) events. */
    //用于存储信号编号到已添加事件的映射。
	struct event_signal_map sigmap;

	/** Priority queue of events with timeouts. */
    //1个优先队列,用于存储具有超时的事件。
	struct min_heap timeheap;

	/** Stored timeval: used to avoid calling gettimeofday/clock_gettime
	 * too often. */
    //用于存储当前时间,以避免频繁调用gettimeofday/clock_gettime。
	struct timeval tv_cache;
	
    //,用于实现单调时钟。
	struct evutil_monotonic_timer monotonic_timer;

	/** Difference between internal time (maybe from clock_gettime) and
	 * gettimeofday. */
    //用于存储内部时间与gettimeofday之间的差异。
	struct timeval tv_clock_diff;
    
	/** Second in which we last updated tv_clock_diff, in monotonic time. */
    //用于存储上次更新tv_clock_diff时的单调时间。
	time_t last_updated_clock_diff;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
	/* threading support */
	/** The thread currently running the event_loop for this base */
	unsigned long th_owner_id;
	/** A lock to prevent conflicting accesses to this event_base */
	void *th_base_lock;
	/** A condition that gets signalled when we're done processing an
	 * event with waiters on it. */
	void *current_event_cond;
	/** Number of threads blocking on current_event_cond. */
	int current_event_waiters;
#endif
	/** The event whose callback is executing right now */
	struct event_callback *current_event;

#ifdef _WIN32
	/** IOCP support structure, if IOCP is enabled. */
    /**:一个指向`event_iocp_port`结构体的指针,如果在Windows平台上使用IOCP(I/O Completion Port)作为事件驱动后端,则使用此字段。
*/
	struct event_iocp_port *iocp;
#endif

	/** Flags that this base was configured with */
    //`flags`:一个枚举类型,用于表示`event_base`的配置标志。
    
	enum event_base_config_flag flags;
	
    //,用于表示最大调度时间。
	struct timeval max_dispatch_time;
    
	int max_dispatch_callbacks;//用于表示最大调度回调数量。
	int limit_callbacks_after_prio;//用于表示在达到特定优先级后限制回调数量。

	/* Notify main thread to wake up break, etc. */
	/** True if the base already has a pending notify, and we don't need
	 * to add any more. */
	int is_notify_pending;//用于表示`event_base`是否已经有一个待处理的通知。
	/** A socketpair used by some th_notify functions to wake up the main
	 * thread. */
    
    //用于free base
	evutil_socket_t th_notify_fd[2];//一个套接字对,用于在另一个线程中唤醒主线程。
	/** An event used by some th_notify functions to wake up the main
	 * thread. */
    
	struct event th_notify;//用于在主线程中唤醒其他线程。

	/** A function used to wake up the main thread from another thread. */
    /**`th_notify`:一个`event`结构体,用于在主线程中唤醒其他线程。*/
	int (*th_notify_fn)(struct event_base *base);

	/** Saved seed for weak random number generator. Some backends use
	 * this to produce fairness among sockets. Protected by th_base_lock. */
    //一个`evutil_weakrand_state`结构体,用于存储弱随机数生成器的种子。
	struct evutil_weakrand_state weakrand_seed;

	/** List of event_onces that have not yet fired. */
    /**`once_events`:一个`LIST_HEAD`结构体,用于存储尚未触发的事件。*/
	LIST_HEAD(once_event_list, event_once) once_events;

};

eventop结构体

/** 用于定义给定事件基础结构的后端的结构体。 */

struct eventop {
    /** 后端的名称。 */
    const char *name;

    /** 初始化函数,用于设置事件基础结构以使用该后端。它应该创建一个新的结构体,
     * 保存运行该后端所需的任何信息,并将其返回。返回的指针将由event_init存储在
     * event_base.evbase字段中。如果初始化失败,该函数应返回NULL。 */
    void *(*init)(struct event_base *);

    /** 启用给定文件描述符或信号的读写事件。'events'参数表示我们要启用的事件类型,
     * 可能是EV_READ、EV_WRITE、EV_SIGNAL和EV_ET的组合。'old'参数表示之前在该
     * 文件描述符上启用的事件。'fdinfo'参数是与文件描述符相关联的结构体,在evmap
     * 中管理;其大小由下面的fdinfo_len字段定义。第一次添加文件描述符时,
     * 它将被设置为0。该函数应在成功时返回0,在错误时返回-1。 */
    int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);

    /** 类似于'add'函数,但'events'参数表示我们要禁用的事件类型。 */
    int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);

    /** 实现事件循环的核心功能。它需要检查哪些已添加的事件已准备就绪,并为每个活动事件
     * 调用event_active函数(通常通过event_io_active等方式)。该函数应在成功时返回0,
     * 在错误时返回-1。 */
    int (*dispatch)(struct event_base *, struct timeval *);

    /** 用于清理和释放事件基础结构中的数据的函数。 */
    void (*dealloc)(struct event_base *);

    /** 标志:如果我们在fork之后需要重新初始化事件基础结构,则设置此标志。 */
    int need_reinit;

    /** 支持的事件方法特性的位数组。 */
    enum event_method_feature features;

    /** 每个具有一个或多个活动事件的文件描述符应记录的额外信息的长度。
     * 此信息作为每个文件描述符的evmap条目的一部分记录,并作为参数传递给上述的
     * 'add'和'del'函数。 */
    size_t fdinfo_len;
};

event_config结构体

/** Internal structure: describes the configuration we want for an event_base
 * that we're about to allocate. */
struct event_config {
    于定义一个双向链表的头部。它将被用作存储event_config_entry类型的结构体的链表。
	TAILQ_HEAD(event_configq, event_config_entry) entries;
    
    //用于提示事件配置所需的CPU数量。
	int n_cpus_hint;
    //指定最大分派间隔的时间值。
	struct timeval max_dispatch_interval;
    //指定在一次循环中最大的分派回调数量。
	int max_dispatch_callbacks;
    //用于限制在特定优先级之后的回调数量。
	int limit_callbacks_after_prio;
    //指定要求的事件方法特性。
    //event_config_require_featureEV_FEATURE_ET:要求支持边沿触发的后端等
	enum event_method_feature require_features;
    //指定事件基础配置的标志。
	enum event_base_config_flag flags;//event_config_set_flag 设置具体可用参数见上文
};
#define TAILQ_HEAD(name, type)			\
struct name {					\
	struct type *tqh_first;			\
	struct type **tqh_last;			\
}

timeval结构体

/* A time value that is accurate to the nearest

  microsecond but also has a range of years.  */

struct timeval

{

 __time_t tv_sec;   /* Seconds.  */

 __suseconds_t tv_usec;  /* Microseconds.  */

};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/753876.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用minio搭建oss

文章目录 1.minio安装1.拉取镜像2.启动容器3.开启端口1.9090端口2.9000端口 4.访问1.网址http://:9090/ 5.创建一个桶 2.minio文件服务基本环境搭建1.创建一个文件模块2.目录结构3.配置依赖3.application.yml 配置4.编写配置类MinioConfig.java&#xff0c;构建minioClient5.Fi…

用Python将PowerPoint演示文稿转换到图片和SVG

PowerPoint演示文稿作为展示创意、分享知识和表达观点的重要工具&#xff0c;被广泛应用于教育、商务汇报及个人项目展示等领域。然而&#xff0c;面对不同的分享场景与接收者需求&#xff0c;有时需要我们将PPT内容以图片形式保存与传播。这样能够避免软件兼容性的限制&#x…

【网络架构】keepalive

目录 一、keepalive基础 1.1 作用 1.2 原理 1.3 功能 二、keepalive安装 2.1 yum安装 2.2 编译安装 三、配置文件 3.1 keepalived相关文件 3.2 主配置的组成 3.2.1 全局配置 3.2.2 配置虚拟路由器 四、实际操作 4.1 lvskeepalived高可用群集 4.2 keepalivedngi…

iOS政策解读之三丨商务、设计和法律 “三重奏“

上一篇的iOS政策解读文章&#xff0c;我们从安全和性能两方面进行了学习和解读&#xff0c;这两个方面是最为重要&#xff0c;也是优先级最高的方面。 如果您还没来得及阅读&#xff0c;欢迎移步我们前两篇的解读文章&#xff1a; iOS政策解读之一丨App提交审核前注意事项必知…

建投数据人力资源管理系统APP完成迭代升级

近日&#xff0c;建投数据人力资源管理系统APP完成迭代升级。 此次升级思路&#xff0c;遵循提升移动应用的功能和用户体验&#xff1b;直观的界面、快速的响应速度和安全的数据存储&#xff1b;个性化的功能&#xff0c;以满足不同员工的需求和使用偏好。 人力资源管理系统A…

ozon定价计算器下载,ozon定价计算器

各位电商卖家们&#xff0c;大家好&#xff01;在这个竞争激烈的电商时代&#xff0c;你是否还在为产品定价而头疼不已&#xff1f;特别是在俄罗斯ozon电商平台&#xff0c;本土与跨境的定价策略更是需要精细把控。今天&#xff0c;就为大家带来一款强大的定价工具——萌啦ozon…

QT QThread 线程类的使用及示例

QThread 是 Qt 框架提供的一个用于处理多线程的类&#xff0c;它允许开发者编写具有并发功能的应用程序&#xff0c;提高程序的响应速度、执行效率和用户体验。 在操作系统中&#xff0c;线程是进程内的执行单元&#xff0c;拥有独立的执行路径。每个线程有自己独立的栈空间&a…

数据库同步最简单的方法

数据库同步到底有咩有简单的方法&#xff0c;有肯定是有的&#xff0c;就看你有咩有缘&#xff0c;看到这篇文章&#xff0c;你就是有缘人。众所周知&#xff0c;数据库同步向来都不是一件简单的事情&#xff0c;它很繁琐&#xff0c;很费精力&#xff0c;很考验经验&#xff0…

unity 导入的模型设置讲解

咱们先讲Model这一栏 Model Scene&#xff1a;场景级属性&#xff0c;例如是否导入灯光和照相机&#xff0c;以及使用什么比例因子。 Scale Factor&#xff1a;缩放因子&#xff08;也就是模型导入后大小如果小了或者大了在这里直接改是相当于该模型的大小的&#xff0c;而且在…

Windows系统开启python虚拟环境

.\env4socre\Scripts\activate : 无法加载文件 E:\SocreMan\env4socre\Scripts\Activate.ps1&#xff0c;因为在此系统上禁止运行脚本。 环境&#xff1a;windows 11、vscode 1、用管理员权限打开powershell 输入set-executionpolicy remotesigned&#xff0c;选择Y 2、返回v…

网工内推 | 网络工程师,IE认证优先,最高18k*14薪,周末双休

01 上海吾索信息科技有限公司 &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;岗位职责&#xff1a; 1&#xff09;具备网络系统运维服务经验以及数据库实施经验&#xff0c;具备网络系统认证相关资质或证书&#xff1b; 2&#xff09;掌握常用各设备的运维巡检…

Logback-打印方法名及代码行号

背景 公司产品使用了logback作为日志输出框架&#xff0c;日志输出的pattern里配置了打印调用方法名及代码行号的配置&#xff0c;但是实际输出的日志方法名总是显示? 在强迫症的驱使下&#xff0c;开启了探秘之旅 Logback版本 1.2.3 项目中Logging.pattern配置如下&#xff1…

【课程总结】Day12:YOLO的深入了解

前言 在【课程总结】Day11&#xff08;下&#xff09;&#xff1a;YOLO的入门使用一节中&#xff0c;我们已经了解YOLO的使用方法&#xff0c;使用过程非常简单&#xff0c;训练时只需要三行代码&#xff1a;引入YOLO&#xff0c;构建模型&#xff0c;训练模型&#xff1b;预测…

DevOps CMDB平台整合Jira工单

背景 在DevOps CMDB平台建设的过程中&#xff0c;我们可以很容易的将业务应用所涉及的云资源&#xff08;WAF、K8S、虚拟机等&#xff09;、CICD工具链&#xff08;Jenkins、ArgoCD&#xff09;、监控、日志等一次性的维护到CMDB平台&#xff0c;但随着时间的推移&#xff0c;…

《昇思25天学习打卡营第5天|onereal》

ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操作&#xff1a;Pointw…

【51单片机入门】点亮数码管

文章目录 前言仿真图如何去绘制一个数字示例代码选择某个数码管显示某个数字 示例代码总结 前言 在嵌入式系统的世界中&#xff0c;单片机扮演着至关重要的角色。51单片机&#xff0c;作为最早的微控制器之一&#xff0c;至今仍被广泛应用在各种设备中。本文将介绍如何使用51单…

嵌入式Linux的浮点运算能力测试

嵌入式Linux的浮点运算能力测试 今天需要对一款ARM CPU的浮点数运算能力进行测试&#xff0c;采用了台式机上常用的SuperPI相同的原理&#xff1a;计算一定小数位数的圆周率来测试硬件的浮点数计算能力和稳定性。 首先下载计算软件的源代码&#xff0c;可以使用下面命令&#…

python使用动态属性访问JSON类数据,Get it!

目录 1、基础访问:内置json模块 📦 1.1 json.loads转换字符串 1.2 动态属性访问技巧 2、面向对象方式处理JSON 🛰️ 2.1 创建JSON数据模型类 2.2 动态属性与方法绑定 3、类型安全: 数据类(dataclasses)应用 🔒 3.1 数据类基础 3.2 自动从JSON解构数据 3.3 动态…

LLM大模型实战 —— DB-GPT阿里云部署指南

简介&#xff1a; DB-GPT 是一个实验性的开源应用&#xff0c;它基于FastChat&#xff0c;并使用vicuna-13b作为基础模型, 模型与数据全部本地化部署, 绝对保障数据的隐私安全。 同时此GPT项目可以直接本地部署连接到私有数据库, 进行私有数据处理&#xff0c; 目前已支持SQL生…

与国际接轨,与行业同行!2024深圳国际自有品牌展今日开幕!

2024深圳国际自有品牌展&#xff08;Marca China International Private Label Fair&#xff0c;简称“Marca China”&#xff09;于6月27日在深圳会展中心&#xff08;福田&#xff09;盛大开幕&#xff01;来自零售、电商、品牌等渠道的买家与专业观众&#xff0c;以及来自全…