libevent是事件驱动的网络库,事件驱动是他的核心,所以理解事件驱动对于理解整个网络库有很重要的意义。
       本着从简入繁,今天分析下单线程最简单的事件触发。通过sample下的event-test来理解libevent的事件驱动。

代码版本为1.4.14。

  libevent事件机制:当事件发生, libevent就会根据用户设定的方式自动执行指定的回调函数,来处理事件。

  这是一种reactor方式的事件通知方式,由事件驱动。reactor的优点:响应快,编程简单等等。。。

首先看下几个重要的结构。等全部分析完libevent,再把全部注释过的代码上传到github上。如果有错误及时告诉我,谢谢。

1.event_base

  我的理解是当前线程中所有事件的一个管理者。位于event-internal.h中。

  

 1 //事件基础管理
2 struct event_base {
3 //I/O复用类型,select、epoll...linux默认是epoll
4 const struct eventop *evsel;
5 //具体的I/O复用,是epollop类型,通过eventop中的init函数返回,包含了具体的I/O复用各种信息
6 void *evbase;
7 //总共的事件个数
8 int event_count; /* counts number of total events */
9 //总共的活动事件个数
10 int event_count_active; /* counts number of active events */
11
12 //退出
13 int event_gotterm; /* Set to terminate loop */
14 //立即退出
15 int event_break; /* Set to terminate loop immediately */
16
17 /* active event management */
18 //活动事件队列,二维链表。第一维是根据优先级,第二维是每个优先级中对应加入的事件
19 struct event_list **activequeues;
20 //优先级队列数量。数组第一维必须告诉大小。因为如果是数组,参入函数,第一维肯定退化为指针,无法知道长度
21 int nactivequeues;
22
23 //信号信息
24 /* signal handling info */
25 struct evsignal_info sig;
26
27 //所有事件队列
28 struct event_list eventqueue;
29
30 //event_base创建时间
31 struct timeval event_tv;
32
33 //event_base时间小根堆
34 struct min_heap timeheap;
35
36 //event_base缓存时间
37 struct timeval tv_cache;
38 };

  2.eventop

  当前选用的I/O复用模型的封装。位于event-internal.h中。

 1 //I/O复用封装
2 struct eventop {
3 const char *name;
4 void *(*init)(struct event_base *); //初始化
5 int (*add)(void *, struct event *); //注册
6 int (*del)(void *, struct event *); //删除
7 int (*dispatch)(struct event_base *, void *, struct timeval *); //事件分发
8 void (*dealloc)(struct event_base *, void *);//释放资源
9 /* set if we need to reinitialize the event base */
10 int need_reinit;
11 };

  3.event

  事件信息的封装

 1 struct event {
2 //事件在队列中的节点(下次分析此队列的实现)
3 TAILQ_ENTRY (event) ev_next;
4 TAILQ_ENTRY (event) ev_active_next;
5 TAILQ_ENTRY (event) ev_signal_next;
6 //事件在最小时间堆中位置
7 unsigned int min_heap_idx; /* for managing timeouts */
8
9 //事件的当前管理类
10 struct event_base *ev_base;
11 //事件对应的文件描述符,一切皆文件
12 int ev_fd;
13 //事件类型
14 short ev_events;
15 //发送到活动队列后要执行的次数
16 short ev_ncalls;
17 //ev_pncalls指向ev_ncalls,允许在回调中将自己的事件执行次数置为0,然后退出
18 short *ev_pncalls; /* Allows deletes in callback */
19
20 //事件触发的时间
21 struct timeval ev_timeout;
22
23 //事件优先级
24 int ev_pri; /* smaller numbers are higher priority */
25
26 //事件到来回调
27 void (*ev_callback)(int, short, void *arg);
28 //事件到来回调的参数
29 void *ev_arg;
30
31 //事件在活动队列中的事件类型,发送给回调函数,让回调函数知道发生事件的原因
32 int ev_res; /* result passed to event callback */
33
34 //标识该事件在哪个队列中,插入的是哪个队列
35 int ev_flags;
36 };

  4.接着看几个比较重要的宏定义

 1 //队列标记
2 //定时器队列,与时间有关的事件加入此队列
3 #define EVLIST_TIMEOUT 0x01
4 //总队列,代表已经插入过
5 #define EVLIST_INSERTED 0x02
6 //信号队列
7 #define EVLIST_SIGNAL 0x04
8 //活动队列
9 #define EVLIST_ACTIVE 0x08
10 //内部队列
11 #define EVLIST_INTERNAL 0x10
12 //初始化队列
13 #define EVLIST_INIT 0x80
14
15 /* EVLIST_X_ Private space: 0x1000-0xf000 */
16 #define EVLIST_ALL (0xf000 | 0x9f)
17 //事件类型,发生了什么事件
18
19 //定时超时,表明事件超时,如果在活动队列中,需要执行
20 #define EV_TIMEOUT 0x01
21 //I/O事件
22 #define EV_READ 0x02
23 #define EV_WRITE 0x04
24 //信号
25 #define EV_SIGNAL 0x08
26 //持续事件
27 #define EV_PERSIST 0x10 /* Persistant event */

  事件机制流程图

  通过流程图可以更加清晰的理解。

测试代码

test.c

 1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <sys/queue.h>
4 #include <sys/time.h>
5 #include <fcntl.h>
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <unistd.h>
10 #include <errno.h>
11 int main(int argc,char **argv){
12 char *input = argv[1];
13 if(argc !=2){
14 input = "hello";
15 }
16 int fd;
17 fd = open("event.fifo",O_WRONLY);
18 if(fd == -1){
19 perror("open error");
20 exit(EXIT_FAILURE);
21 }
22 write(fd,input,strlen(input));
23 close(fd);
24 printf("write success\n");
25 return 0;
26 }

1.首先运行./event-test

可以看到linux默认的I/O复用是epoll。加入队列,并且不是定时函数。最后进入循环到达epoll_dispatch分发。

2.另一个终端中运行./test

此时上面一个终端可以收到如下信息:事件触发,调用event_del,从相应队列中删除,然后执行,event-test.c中再次加入了事件,进入事件循环。

具体函数注释:

1.event_base_new

 1 //创建event_base
2 struct event_base *
3 event_base_new(void)
4 {
5 int i;
6 struct event_base *base;
7 //申请空间
8 if ((base = calloc(1, sizeof(struct event_base))) == NULL)
9 event_err(1, "%s: calloc", __func__);
10
11 event_sigcb = NULL;
12 event_gotsig = 0;
13 //是否使用绝对时间
14 detect_monotonic();
15 //获取event_base创建时间
16 gettime(base, &base->event_tv);
17
18 //初始化小根堆
19 min_heap_ctor(&base->timeheap);
20 //初始化队列
21 TAILQ_INIT(&base->eventqueue);
22 //信号相关
23 base->sig.ev_signal_pair[0] = -1;
24 base->sig.ev_signal_pair[1] = -1;
25
26 base->evbase = NULL;
27 //获得I/O复用,选到合适的就往下执行。linux默认是epoll
28 for (i = 0; eventops[i] && !base->evbase; i++) {
29 //获得I/O复用
30 base->evsel = eventops[i];
31 //获得具体的I/O复用信息
32 base->evbase = base->evsel->init(base);
33 }
34 //没有I/O复用,报错退出
35 if (base->evbase == NULL)
36 event_errx(1, "%s: no event mechanism available", __func__);
37 //如果设置了EVENT_SHOW_METHOD,输出IO复用名字
38 if (evutil_getenv("EVENT_SHOW_METHOD"))
39 event_msgx("libevent using: %s\n",
40 base->evsel->name);
41
42 /* allocate a single active event queue */
43 //初始化活动队列的优先级,默认优先级为1
44 event_base_priority_init(base, 1);
45
46 return (base);
47 }

2.event_base_priority_init

 1 //初始化优先队列
2 int
3 event_base_priority_init(struct event_base *base, int npriorities)
4 {
5 int i;
6 //如果base中有活动事件,返回,不处理优先级的初始化
7 if (base->event_count_active)
8 return (-1);
9 //如果优先级数量未变,没有必要执行
10 if (npriorities == base->nactivequeues)
11 return (0);
12 //释放所有优先级队列
13 if (base->nactivequeues) {
14 for (i = 0; i < base->nactivequeues; ++i) {
15 free(base->activequeues[i]);
16 }
17 free(base->activequeues);
18 }
19
20 /* Allocate our priority queues */
21 //分配优先级队列
22 base->nactivequeues = npriorities;
23 base->activequeues = (struct event_list **)
24 calloc(base->nactivequeues, sizeof(struct event_list *));
25 if (base->activequeues == NULL)
26 event_err(1, "%s: calloc", __func__);
27 //默认每个优先级分配一个节点,作为事件队列的队列的头结点
28 for (i = 0; i < base->nactivequeues; ++i) {
29 base->activequeues[i] = malloc(sizeof(struct event_list));
30 if (base->activequeues[i] == NULL)
31 event_err(1, "%s: malloc", __func__);
32 //每个事件都初始化为队列的头结点
33 TAILQ_INIT(base->activequeues[i]);
34 }
35
36 return (0);
37 }

3.event_set

 1 //设置与注册event
2 //ev: 需要注册的事件
3 //fd: 文件描述符
4 //events: 注册事件的类型
5 //callback: 注册事件的回调函数
6 //arg: 注册事件回调函数的参数
7 //事件类型有:
8 //#define EV_TIMEOUT 0x01
9 //#define EV_READ 0x02
10 //#define EV_WRITE 0x04
11 //#define EV_SIGNAL 0x08
12 //定时事件event_set(ev, -1, 0, cb, arg)
13 void
14 event_set(struct event *ev, int fd, short events,
15 void (*callback)(int, short, void *), void *arg)
16 {
17 /* Take the current base - caller needs to set the real base later */
18 //默认为全局ev_base进行事件的注册
19 ev->ev_base = current_base;
20 //事件回调
21 ev->ev_callback = callback;
22 //事件回调参数
23 ev->ev_arg = arg;
24 //对应文件描述符
25 ev->ev_fd = fd;
26 //事件类型
27 ev->ev_events = events;
28 //事件在活动队列中的类型
29 ev->ev_res = 0;
30 //标识事件加入了哪个队列
31 ev->ev_flags = EVLIST_INIT;
32 //加入活动队列后调试的次数
33 ev->ev_ncalls = 0;
34 //Allows deletes in callback,允许在回调中删除自己
35 ev->ev_pncalls = NULL;
36 //初始化事件在堆中的位置。刚开始为-1
37 min_heap_elem_init(ev);
38
39 /* by default, we put new events into the middle priority */
40 //默认事件的优先级为中间
41 if(current_base)
42 ev->ev_pri = current_base->nactivequeues/2;
43 }

4.event_add

 1 //事件加入队列
2 int
3 event_add(struct event *ev, const struct timeval *tv)
4 {
5 //事件的基础管理,事件中有一个event_base指针,指向了他所属于的管理类
6 struct event_base *base = ev->ev_base;
7 //当前I/O复用管理,包括初始化,注册,回调等。。。
8 const struct eventop *evsel = base->evsel;
9 //具体的I/O复用
10 void *evbase = base->evbase;
11 int res = 0;
12
13 event_debug((
14 "event_add: event: %p, %s%s%scall %p",
15 ev,
16 ev->ev_events & EV_READ ? "EV_READ " : " ",
17 ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
18 tv ? "EV_TIMEOUT " : " ",
19 ev->ev_callback));
20
21 assert(!(ev->ev_flags & ~EVLIST_ALL));
22
23 /*
24 * prepare for timeout insertion further below, if we get a
25 * failure on any step, we should not change any state.
26 */
27 //事件的时间tv不为null并且现在事件还不在定时队列中,我们先在小根堆中申请一个位置,以便后面加入
28 //event_set后事件的ev_flags为EVLIST_INIT
29 if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
30 if (min_heap_reserve(&base->timeheap,
31 1 + min_heap_size(&base->timeheap)) == -1)
32 return (-1); /* ENOMEM == errno */
33 }
34 //如果事件类型是EV_READ,EV_WRITE,EV_SIGNAL并且事件状态不是EVLIST_INSERTED(已加入)与EVLIST_ACTIVE(已活动)
35 if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
36 !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
37 //将事件加入到对应的I/O复用中
38 res = evsel->add(evbase, ev);
39 if (res != -1)
40 //加入对应的I/O复用成功后,插入EVLIST_INSERTED队列
41 event_queue_insert(base, ev, EVLIST_INSERTED);
42 }
43
44 /*
45 * we should change the timout state only if the previous event
46 * addition succeeded.
47 */
48 //定时执行事件处理(tv不为零,表示有超时时间)
49 if (res != -1 && tv != NULL) {
50 struct timeval now;
51
52 /*
53 * we already reserved memory above for the case where we
54 * are not replacing an exisiting timeout.
55 */
56 //定时事件已经在定时队列中了,先从中删除
57 if (ev->ev_flags & EVLIST_TIMEOUT)
58 event_queue_remove(base, ev, EVLIST_TIMEOUT);
59
60 /* Check if it is active due to a timeout. Rescheduling
61 * this timeout before the callback can be executed
62 * removes it from the active list. */
63 //定时事件是否在活动队列中,并且是定时事件,如果是,从活动队列中删除
64 if ((ev->ev_flags & EVLIST_ACTIVE) &&
65 (ev->ev_res & EV_TIMEOUT)) {
66 /* See if we are just active executing this
67 * event in a loop
68 */
69 //调用次数置零
70 if (ev->ev_ncalls && ev->ev_pncalls) {
71 /* Abort loop */
72 *ev->ev_pncalls = 0;
73 }
74 //从活动队列中删除
75 event_queue_remove(base, ev, EVLIST_ACTIVE);
76 }
77
78 //得到当前时间
79 gettime(base, &now);
80 //更新时间
81 //当前时间点+定时事件每隔多少秒触发时间=触发时间点。ev->ev_timeout为事件触发时间点
82 evutil_timeradd(&now, tv, &ev->ev_timeout);
83
84 event_debug((
85 "event_add: timeout in %ld seconds, call %p",
86 tv->tv_sec, ev->ev_callback));
87 //加入定时队列
88 event_queue_insert(base, ev, EVLIST_TIMEOUT);
89 }
90
91 return (res);
92 }

5.event_base_loop

  1 /* not thread safe */
2 //默认进入全局事件管理的事件循环
3 int
4 event_loop(int flags)
5 {
6 return event_base_loop(current_base, flags);
7 }
8 //事件分发,进入事件循环,默认进入全局事件管理的事件循环
9 int
10 event_base_loop(struct event_base *base, int flags)
11 {
12 //I/O复用管理
13 const struct eventop *evsel = base->evsel;
14 //具体I/O复用
15 void *evbase = base->evbase;
16 struct timeval tv;
17 struct timeval *tv_p;
18 int res, done;
19
20 /* clear time cache */
21 base->tv_cache.tv_sec = 0;
22 //信号处理
23 if (base->sig.ev_signal_added)
24 evsignal_base = base;
25 done = 0;
26 //事件循环
27 while (!done) {
28 /* Terminate the loop if we have been asked to */
29 //退出
30 if (base->event_gotterm) {
31 base->event_gotterm = 0;
32 break;
33 }
34 //立即退出
35 if (base->event_break) {
36 base->event_break = 0;
37 break;
38 }
39
40 /* You cannot use this interface for multi-threaded apps */
41 //信号处理
42 while (event_gotsig) {
43 event_gotsig = 0;
44 if (event_sigcb) {
45 res = (*event_sigcb)();
46 if (res == -1) {
47 errno = EINTR;
48 return (-1);
49 }
50 }
51 }
52
53 //检测时间对不对,不对的话要校准
54 timeout_correct(base, &tv);
55 //tv为当前时间
56 tv_p = &tv;
57 //如果当前事件活动队列为0,并且事件是阻塞的,立马到时间堆中去查找定时时间
58 if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
59 timeout_next(base, &tv_p);
60 } else {
61 /*
62 * if we have active events, we just poll new events
63 * without waiting.
64 */
65 //活动队列不为空,或者此事件是非阻塞事件,将超时时间置为零,意味着没有超时时间
66 evutil_timerclear(&tv);
67 }
68 //没有可以执行的事件,退出
69 /* If we have no events, we just exit */
70 if (!event_haveevents(base)) {
71 event_debug(("%s: no events registered.", __func__));
72 return (1);
73 }
74
75 /* update last old time */
76 //更新base的创建时间
77 gettime(base, &base->event_tv);
78
79 /* clear time cache */
80 //清缓存
81 base->tv_cache.tv_sec = 0;
82
83 //进行对应事件的分发,将tv_p也传入进去,tv_p为超时时间
84 res = evsel->dispatch(base, evbase, tv_p);
85
86 if (res == -1)
87 return (-1);
88 //来事件了
89 //更新缓存时间
90 gettime(base, &base->tv_cache);
91
92 //进行超时处理,处理目前时间已经到达需要执行的事件,加入活动队列等操作
93 timeout_process(base);
94
95 //有活动队列
96 if (base->event_count_active) {
97 //调用
98 event_process_active(base);
99 //全部执行完,并且只要执行一次,就可以跳出循环了
100 if (!base->event_count_active && (flags & EVLOOP_ONCE))
101 done = 1;
102 } else if (flags & EVLOOP_NONBLOCK)
103 //活动队列没有事件,而且是非阻塞,跳出循环
104 done = 1;
105 }
106
107 /* clear time cache */
108 base->tv_cache.tv_sec = 0;
109
110 event_debug(("%s: asked to terminate loop.", __func__));
111 return (0);
112 }

6.timeout_next

 1 //查找下一个需要处理的事件,这边需要指针的指针,因为假如小根堆中压根没有事件,将指针置为空
2 static int
3 timeout_next(struct event_base *base, struct timeval **tv_p)
4 {
5 struct timeval now;
6 struct event *ev;
7 struct timeval *tv = *tv_p;
8 //查找小根堆里面的事件最小的事件,没有就退出
9 if ((ev = min_heap_top(&base->timeheap)) == NULL) {
10 /* if no time-based events are active wait for I/O */
11 //没有事件了,超时时间置为空,退出,时间指针置为空,所以需要指针的指针
12 *tv_p = NULL;
13 return (0);
14 }
15
16 if (gettime(base, &now) == -1)
17 return (-1);
18 //事件已经超时,需要立即执行,清空tv_p,超时时间为0,返回
19 if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
20 evutil_timerclear(tv);
21 return (0);
22 }
23 //事件还没有到执行的时间,计算出相差的时间,返回
24 evutil_timersub(&ev->ev_timeout, &now, tv);
25
26 assert(tv->tv_sec >= 0);
27 assert(tv->tv_usec >= 0);
28
29 event_debug(("timeout_next: in %ld seconds", tv->tv_sec));
30 return (0);
31 }

7.timeout_process

 1 //进行时间处理
2 void
3 timeout_process(struct event_base *base)
4 {
5 struct timeval now;
6 struct event *ev;
7 //时间堆为空退出
8 if (min_heap_empty(&base->timeheap))
9 return;
10
11 gettime(base, &now);
12
13 //事件执行时间比现在大时,需要执行,将此事件从event队列中删除
14 while ((ev = min_heap_top(&base->timeheap))) {
15 if (evutil_timercmp(&ev->ev_timeout, &now, >))
16 break;
17
18 /* delete this event from the I/O queues */
19 //从ev对应的队列中删除此事件
20 event_del(ev);
21
22 event_debug(("timeout_process: call %p",
23 ev->ev_callback));
24 //发送到活动队列,激活此事件,事件的状态变更为EV_TIMEOUT,事件的执行次数改为1
25 event_active(ev, EV_TIMEOUT, 1);
26 }
27 }

8.event_process_active

 1 //对在活动队列中的事件调用他对应的回调
2 static void
3 event_process_active(struct event_base *base)
4 {
5 struct event *ev;
6 struct event_list *activeq = NULL;
7 int i;
8 short ncalls;
9
10 //取得第一个非空的优先级队列,nactivequeues越小,优先级越高
11 for (i = 0; i < base->nactivequeues; ++i) {
12 if (TAILQ_FIRST(base->activequeues[i]) != NULL) {
13 activeq = base->activequeues[i];
14 break;
15 }
16 }
17
18 assert(activeq != NULL);
19
20 for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
21 //如果是持续事件,只从EVLIST_ACTIVE队列中删除事件即可
22 if (ev->ev_events & EV_PERSIST)
23 event_queue_remove(base, ev, EVLIST_ACTIVE);
24 else
25 event_del(ev);
26
27 /* Allows deletes to work */
28 //允许删除自己
29 ncalls = ev->ev_ncalls;
30 ev->ev_pncalls = &ncalls;
31 while (ncalls) {
32 //持续调用,直到调用次数为0
33 ncalls--;
34 ev->ev_ncalls = ncalls;
35 (*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg);
36 if (event_gotsig || base->event_break)
37 return;
38 }
39 }
40 }

libevent中的事件机制的更多相关文章

  1. jQuery中的事件机制深入浅出

    昨天呢,我们大家一起分享了jQuery中的样式选择器,那么今天我们就来看一下jQuery中的事件机制,其实,jQuery中的事件机制与JavaScript中的事件机制区别是不大的,只是,JavaScr ...

  2. Tomcat与Spring中的事件机制详解

    最近在看tomcat源码,源码中出现了大量事件消息,可以说整个tomcat的启动流程都可以通过事件派发机制串起来,研究透了tomcat的各种事件消息,基本上对tomcat的启动流程也就有了一个整体的认 ...

  3. Spring 中的事件机制

    说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎 ...

  4. nodeJS中的事件机制

    events模块是node的核心模块,几乎所有常用的node模块都继承了events模块,比如http.fs等.本文将详细介绍nodeJS中的事件机制 EventEmitter 多数 Node.js ...

  5. javascript 中的事件机制

    1.javascript中的事件. 事件流 javascript中的事件是以一种流的形式存在的. 一个事件会也有多个元素同时响应. 有时候这不是我们想要的效果, 我们只是需要某个特定的元素相应我们的绑 ...

  6. Flex中利用事件机制进行主程序与子窗体间参数传递

    在开发具有子窗体,或者itemrenderer的应用时,常常涉及到子窗体向父窗体传递参数或者从itemrenderer内的控件向外部的主程序传递参数的需求.这些都可以通过事件机制这一统一方法加以解决. ...

  7. qt中的事件机制

    事件 1.QEvent -->类型 -> QKeyEvent QEvent::KeyRelease QEvent::MouseMove -> QMouseEvent 2.事件处理过程 ...

  8. C#中的事件机制

    这几天把事件学了一下,总算明白了一些.不多说了,直接代码. class Program { static void Main(string[] args) { CatAndMouse h = new ...

  9. android中的事件传递和处理机制

    一直以来,都被android中的事件传递和处理机制深深的困扰!今天特意来好好的探讨一下.现在的感觉是,只要你理解到位,其实事件的 传递和处理机制并没有想象中的那么难.总之,不要自己打击自己,要相信自己 ...

随机推荐

  1. OO Unit4总结 & 结课总结

    OO Unit4总结 & 结课总结 OO课Unit4 UML解析应用技术回顾 BUAA.1823.邓新宇 2020/6/19 总结本单元三次作业的架构设计 本单元的架构设计主要是两方面. 一方 ...

  2. 《机器学习Python实现_10_06_集成学习_boosting_gbdt分类实现》

    一.利用回归树实现分类 分类也可以用回归树来做,简单说来就是训练与类别数相同的几组回归树,每一组代表一个类别,然后对所有组的输出进行softmax操作将其转换为概率分布,然后再通过交叉熵或者KL一类的 ...

  3. qta自动化

    qta框架采用PO(page object)模式,即页面结构层和逻辑对象层,如图的用例结构:我们将页面结构放到lib层,将执行用例层放到test层,区分开方便维护:

  4. 线程操作与进程挂起(Windows核心编程)

    0x01 线程挂起与切换 对于挂起进程,挂起线程则比较简单,利用 ResumeThread 与 SuspendThread 即可,当线挂起时线程用户状态下的一切操作都会暂停 #include < ...

  5. springboot 项目中css js 等静态资源无法访问的问题

    目录 问题场景 问题分析 问题解决 问题场景 今天在开发一个springboot 项目的时候突然发现 css js 等静态资源竟然都报404找不到,折腾了好久终于把问题都解决了,决定写篇博客,纪录总结 ...

  6. JavaWeb——MySQL约束

    内容索引 1. DQL:查询语句 1. 排序查询 2. 聚合函数 3. 分组查询 4. 分页查询 2. 约束 3. 多表之间的关系 4. 范式 5. 数据库的备份和还原 DQL:查询语句 1. 排序查 ...

  7. Spring cloud 基础框架集成

    Spring cloud 基础框架集成 1. 注册中心 -eurekar 1. pom依赖 <?xml version="1.0" encoding="UTF-8& ...

  8. Java_接口回调与匿名内部类

    匿名内部类 警告:匿名内部类本质上是一个对象 如果有一个接口或者抽象类,必须要用class定义一个实现类写重写抽象方法,才能创建对象并使用. 匿名内部类就是省略了用class定义子类的过程,直接使用父 ...

  9. Map&Set的理解

    Set子接口 特点:无序.无下标.元素不可重复. 方法:全部继承自Collection中的方法. Set实现类 HashSet: 基于HashCode实现了不重复. 当存入元素的哈希码相同时,会调用e ...

  10. Docker部署微服务项目

    测试包准备工作 1.spring.io或者ide创建demo工程 spring官网 2.本地demo代码,打包成jar包 使用Dockerfile构建微服务镜像 3.将jar包上传到你的vps lin ...