Linux 编程--三种常用的定时器
这节我们来探讨一下linux开发过程中常用的定时器,尤其在网络编程中被常常用到如heartbeat,断线重连等等。这里提供了三种定时器的方案,分别是链表形式的计时器,环型计时器,最小堆计时器。每个都有不同的作用和优势,可以结合实际项目选择或者改良。
链表计时器:
链表计时器是一个实现很简单的一种计时器,可以使用单链表或者双链表来实现,我这里有一个双链表实现的例子
/*** timer list******/ #ifndef LIST_TIMER_H #define LIST_TIMER_H #include <time.h>typedef struct util_time{struct util_time * prev; // 双链表的前驱struct util_time * next; //双链表后驱//Client data pointvoid * cdata; //其他数据//timeout valuetime_t out_time; //定时时间int persist; //是否是坚持定时//if timeout callbackvoid (*timeout_callback)(void * data); //定时回调函数 }UTIL_TIME;struct list_timer;struct TimerOP {int (*add)(struct list_timer * lt, UTIL_TIME * ut);int (*del)(struct list_timer * lt, UTIL_TIME ** ut);int (*adjust)(struct list_timer * lt, UTIL_TIME * ut, time_t _time);void (*_tick)(struct list_timer * lt); };//Timer list //定时器链表头 typedef struct list_timer {struct TimerOP timer_op;UTIL_TIME * head;UTIL_TIME * tail; }LIST_TIMER;//timer list operator, all function only handle list and free timer, not free clientdata int add_timer(LIST_TIMER * lt, UTIL_TIME * ut); int del_timer(LIST_TIMER * lt, UTIL_TIME **ut); int adjust_timer(LIST_TIMER * lt, UTIL_TIME * ut, time_t _time); void tick(LIST_TIMER * lt); void init_List_Timer(LIST_TIMER * lt); void destroy_list_Timer(LIST_TIMER * lt); #endif
View Code
#include <stdlib.h> #include <string.h> #include <signal.h> #include <unistd.h> #include <stdio.h> #include "list_timer.h"/*** @brief init_List_Timer* @param lt timer list* initialize list timer, head and tail are NULL* 初始化定时器*/ void init_List_Timer(LIST_TIMER *lt) {lt->timer_op.add = add_timer;lt->timer_op.del = del_timer;lt->timer_op.adjust = adjust_timer;lt->timer_op._tick = tick;lt->head = NULL;lt->tail = NULL; }/*** @brief add_timer* @param lt* @param ut* @return* 添加一个定时器 并跟定时器排序*/ int add_timer(LIST_TIMER * lt, UTIL_TIME * ut) {if (lt && ut){if (!lt->head && !lt->tail){lt->head = ut;lt->tail = ut;ut->prev = NULL;ut->next = NULL;return 1;}else if (lt->head && lt->tail){UTIL_TIME * temp = lt->head;UTIL_TIME * tempbak = NULL;while(temp){if((temp->out_time) > (ut->out_time)){if(lt->head == temp){ut->next = temp;temp->prev = ut;lt->head = ut;ut->prev = NULL;return 1;}}tempbak = temp;temp = temp->next;}tempbak->next = ut;ut->prev = tempbak;ut->next = NULL;lt->tail = ut;return 1;}return 0;}return 0; }/*** @brief del_timer* @param lt* @param ut* @return* 由相应定时器的位置删除定时器*/ int del_timer(LIST_TIMER * lt, UTIL_TIME **ut) {if(lt && ut && *ut){if(lt->head){UTIL_TIME *temp = *ut;if((temp == lt->head) && (temp != lt->tail)) //头 {lt->head = temp->next;temp->next->prev = NULL;}else if((temp == lt->tail) && (temp != lt->head)) //尾 {temp->prev->next = NULL;lt->tail = temp->prev;}else if((temp == lt->tail) && (temp == lt->head)) //只有一个定时器 {lt->head = NULL;lt->tail = NULL;}else{temp->next->prev = temp->prev;temp->prev->next = temp->next;}(*ut)->cdata = NULL;(*ut)->next = NULL;(*ut)->prev = NULL;(*ut)->timeout_callback = NULL;free(*ut);*ut = NULL;ut = NULL;return 1;}}return 0; }/*** @brief adjust_timer* @param lt* @param ut* @param _time* @return* if a timer be deleted or addition time to a timer, adjust timer list* 移除指针并插入*/ int adjust_timer(LIST_TIMER * lt, UTIL_TIME * ut, time_t _time) {if(!lt || !ut){return 0;}ut->out_time = time(NULL) + _time;if(!ut->prev && !ut->next){return 1; //only have single Node}else if (ut->prev && !ut->next) {ut->prev->next = NULL; //if ut is tail Node, remove it.ut->prev = NULL;}else if (!ut->prev && ut->next) {lt->head = ut->next; //if ut is head Nodeut->next->prev = NULL;ut->next = NULL;ut->prev = NULL;}else{ut->next->prev = ut->prev; //ut is middleut->prev->next = ut->next;ut->next = NULL;ut->prev = NULL;}// Can be optimized , insert after this Node.if(add_timer(lt, ut)) //reinsert it {return 1;}return 0; }/*** @brief tick* @param lt* Timer list tick, depend on callback function, adjust timer.*/ void tick(LIST_TIMER * lt) {if(lt){time_t now = time(NULL);UTIL_TIME *temp = lt->head;UTIL_TIME *tempbak = temp;while(temp){tempbak = temp;temp = temp->next;if(tempbak->out_time <= now) //检查时间 {tempbak->timeout_callback(tempbak->cdata);if(!(tempbak->persist)) del_timer(lt, &tempbak);else{//persist time 重新调整adjust_timer(lt, tempbak, tempbak->persist);}}else{break;}}return;}return; }/*** @brief destroy_list_Timer* @param lt* destroy timer list*/ void destroy_list_Timer(LIST_TIMER *lt) {if(!lt){return;}UTIL_TIME * temp = lt->head;UTIL_TIME * tempnext = NULL;while(temp){tempnext = temp->next;temp->cdata = NULL;temp->next = NULL;temp->prev = NULL;temp->timeout_callback = NULL;free(temp);temp = tempnext;}lt->head = NULL;lt->tail = NULL; }
View Code
测试用例
#include "list_timer.h" #include <stdio.h> #include <unistd.h> #include <signal.h> #include <stdlib.h>enum {CONNECTED, READ, WROTE, NORMAL};typedef struct ClientData{int fd;char ipaddr[4];char * data;//This client monitor statusint flags;//Timer point, if ut is null that mean not included into timer list.struct util_time * ut; }CLIENTDATA; LIST_TIMER lt;void doit(void * mydata) {CLIENTDATA * data = (CLIENTDATA *)mydata;if(data){switch(data->flags){case CONNECTED:fprintf(stderr, "don`t delete\n");break;case READ:fprintf(stderr, "delete \n");break;case WROTE:break;case NORMAL:break;default:break;}} }int main() {init_List_Timer(<);CLIENTDATA * cl1 = (CLIENTDATA *)malloc(sizeof(CLIENTDATA));cl1->data = NULL;cl1->fd = 23;cl1->flags = CONNECTED;CLIENTDATA * cl2 = (CLIENTDATA *)malloc(sizeof(CLIENTDATA));cl2->data = NULL;cl2->fd = 2324;cl2->flags = READ;UTIL_TIME *ut2 = (UTIL_TIME *)malloc(sizeof(UTIL_TIME));ut2->timeout_callback = doit;ut2->cdata = cl2;ut2->out_time = time(NULL) + 2;ut2->persist = 0;UTIL_TIME *ut1 = (UTIL_TIME *)malloc(sizeof(UTIL_TIME));ut1->timeout_callback = doit;ut1->cdata = cl1;ut1->out_time = time(NULL)+ 6;ut1->persist = 10;add_timer(<, ut1);add_timer(<, ut2);while(1){tick(<);usleep(500);}return 0; }
View Code
list_timer.c list_timer.h 其中list_main.c为测试实例。 其中的tick函数 可以放在一个死循环中,这样就实现了简单的定时,每个定时器按照事件间隔的从小到大的顺序并使用双链表进行组织,若定时器为persist的话就反复调整计时器,加上每次间隔时间并重新调整链表。在tick中比对头个计时器的时间如果小于目前时间就执行相应回调函数。
如在epoll模型的服务器中若要加入某个定时器,可以这样做
while(1) {tick(timer); epoll_wait(); //此处得到定时器的最短计时, 即双链表表头定时间隔作为 阻塞时间。//其他for(;;){}}
此种定时器的效率跟链表操作相同插入 删除都是logN,调整计时器在链表的位置花费较多的时间(去掉重新插入)。
环形定时器:
这种定时器就像钟表一样,如秒针一样一秒移动一次,当秒针移动到相应位置时,查看此位置是否有定时器,若有就不断的check时间。这里的某个指针位置的定时器可以以链表形式由圈数量从小到大排列。指针走到某个位置查看链表的圈数若为0则表示时间到了 否则将圈数减1等待下次调用。如图所示:
这里给出实例代码如下:
#ifndef ROUND_TIMER_H #define ROUND_TIMER_H #include <time.h>typedef struct aw_timer{void *data;struct aw_timer * prev;struct aw_timer * next;time_t expect;//current slot numberint slotnumber;//timer loop cercle numberint loopcercle;//weather persistint persist;//if return 0 : mean don`t delete timer//return 1 : delete timer.void (*timeout_callback)(void * data); }AW_TIMER;#define N 60 typedef struct wh_timers{//time gap 1 sec or 1 min or 1 hourint timegap;//current slot point locetionint curslotpoint;//timer list pointAW_TIMER *slot[N]; }WH_TIMERS;void init_wh_timer(WH_TIMERS * wt, int timegap); int add_wh_timer(WH_TIMERS * wt, AW_TIMER *at); int del_wh_timer(WH_TIMERS * wt, AW_TIMER **at, int remove); int adjust_wh_timer(WH_TIMERS * wt, AW_TIMER *at); void wh_tick(WH_TIMERS * wt); void destory_wh_timer(WH_TIMERS * wt);#endif
View Code
#include <time.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <signal.h> #include "round_timer.h"/*** @brief insert_sort_timer* @param aw_list* @param at* sort timer depend on loopcercle* 按照圈数插入*/ static void insert_sort_timer(AW_TIMER ** aw_list, AW_TIMER **at) {if(aw_list && at){AW_TIMER * atr = *aw_list;while(atr){if(((*at)->loopcercle <= (atr->loopcercle)) && (!atr->prev)){(*at)->next = atr;atr->prev = *at;(*at)->prev = NULL;*aw_list = *at;return;}else if(((*at)->loopcercle >= (atr->loopcercle)) && (!atr->next)) {atr->next = *at;(*at)->prev = atr;(*at)->next = NULL;return;}else if(((*at)->loopcercle <= (atr->loopcercle)) && atr->next && atr->prev){(*at)->next = atr;(*at)->prev = atr->prev;atr->prev->next = *at;atr->prev = *at;return;}atr = atr->next;}} }/*** @brief init_wh_timer* @param wt* @param timegap* Initialize wheel timers, timegap mean seconds one step.* If timegap < 0 , timegap = 1*/ void init_wh_timer(WH_TIMERS * wt, int timegap) {if(wt){wt->curslotpoint = 0;wt->timegap = timegap ? timegap : 1;int i;for(i = 0; i < N; ++i){wt->slot[i] = NULL;}} }/*** @brief add_wh_timer* @param wt* @param at* @return* add a timer into wheel timers*/ int add_wh_timer(WH_TIMERS *wt, AW_TIMER *at) {if(wt && at){if(at->expect){//get timer loop cercles.at->loopcercle = (at->expect) / ((wt->timegap) * N);//get timer slot number in wheelat->slotnumber = (wt->curslotpoint + ((at->expect) / (wt->timegap))) % N;int index_t = at->slotnumber;if(wt->slot[index_t]){//If this slot is not empty, insert it sortable.insert_sort_timer(&(wt->slot[index_t]), &at);}else{wt->slot[index_t] = at;at->prev = NULL;at->next = NULL;}}} }/*** @brief del_wh_timer* @param wt* @param at* @param remove* @return* delete a timer, and the remove flag mean weather free it or not.*/ int del_wh_timer(WH_TIMERS *wt, AW_TIMER **at, int remove) {if(wt && at){if(*at){if(!((*at)->prev)){wt->slot[(*at)->slotnumber] = (*at)->next;if((*at)->next)(*at)->next->prev = NULL;}else if(!((*at)->next)){if((*at)->prev)(*at)->prev->next = NULL;elsewt->slot[(*at)->slotnumber] = NULL;}else{(*at)->prev->next = (*at)->next;(*at)->next->prev = (*at)->prev;(*at)->prev = NULL;(*at)->next = NULL;}//是否真的移除if(remove){free(*at);*at = NULL;}}return 0;}return 0; }/*** @brief adjust_wh_timer* @param wt* @param at* @return* reset timer, fristly, will remove timer, then insert wheel again.*/ int adjust_wh_timer(WH_TIMERS *wt, AW_TIMER *at) {//调整计时器if(wt && at){del_wh_timer(wt, &at, 0);add_wh_timer(wt, at);} }/*** @brief wh_tick* @param wt* point move step by step, if slot is not null, run timeout callback function.* if loop cercle sum > 0, will skip it.*/ void wh_tick(WH_TIMERS *wt) {if(wt){wt->curslotpoint = (wt->curslotpoint + 1)%N;if(wt->slot[wt->curslotpoint]){AW_TIMER * at = wt->slot[wt->curslotpoint];AW_TIMER *attemp = at;while(at){attemp = at->next;if(0 >= at->loopcercle){//圈数为0 开始调用at->timeout_callback(at->data);if(at->persist){adjust_wh_timer(wt, at);}else{del_wh_timer(wt, &at, 1);}}else{//等待下次被调 并停止循环(at->loopcercle)--;break;}at = attemp;}}alarm(wt->timegap);} }/*** @brief destory_wh_timer* @param wt* free timer wheel.*/ void destory_wh_timer(WH_TIMERS *wt) {int i;for(i = 0; i < N; ++i){if(wt->slot[i]){AW_TIMER * timer = wt->slot[i];AW_TIMER * temptimer = timer;while(timer){temptimer = timer->next;free(timer);timer = NULL;timer = temptimer;}temptimer = NULL;wt->slot[i] = NULL;}} }
View Code
#include "list_timer.h" #include <stdio.h> #include <unistd.h> #include <signal.h> #include <stdlib.h>enum {CONNECTED, READ, WROTE, NORMAL};typedef struct ClientData{int fd;char ipaddr[4];char * data;//This client monitor statusint flags;//Timer point, if ut is null that mean not included into timer list.struct util_time * ut; }CLIENTDATA; LIST_TIMER lt;void doit(void * mydata) {CLIENTDATA * data = (CLIENTDATA *)mydata;if(data){switch(data->flags){case CONNECTED:fprintf(stderr, "don`t delete\n");break;case READ:fprintf(stderr, "delete \n");break;case WROTE:break;case NORMAL:break;default:break;}} }int main() {init_List_Timer(<);CLIENTDATA * cl1 = (CLIENTDATA *)malloc(sizeof(CLIENTDATA));cl1->data = NULL;cl1->fd = 23;cl1->flags = CONNECTED;CLIENTDATA * cl2 = (CLIENTDATA *)malloc(sizeof(CLIENTDATA));cl2->data = NULL;cl2->fd = 2324;cl2->flags = READ;UTIL_TIME *ut2 = (UTIL_TIME *)malloc(sizeof(UTIL_TIME));ut2->timeout_callback = doit;ut2->cdata = cl2;ut2->out_time = time(NULL) + 2;ut2->persist = 0;UTIL_TIME *ut1 = (UTIL_TIME *)malloc(sizeof(UTIL_TIME));ut1->timeout_callback = doit;ut1->cdata = cl1;ut1->out_time = time(NULL)+ 6;ut1->persist = 10;add_timer(<, ut1);add_timer(<, ut2);while(1){tick(<);usleep(500);}return 0; }
View Code
我们可以看到在例子中 我用了定时信号来模拟一秒走一次,我们注意到这里信号可能会造成某些系统调用的中断 这里可以将它该为类似链表计时器中那样,在循环中得到俩次时间间隔 若大于等于1s就将秒针加1, 不过这样可能就不太准确了。可以将时间间隔设置的稍微大一些。
最小堆计时器:
最小堆计时器是比较常用的一种计时器,在libevent中可以看到它的使用。这种数据结构每次返回的是最小值时间间隔定时器。Libevent 事件循环(1) 这里可以简单看一下它的用法。当添加计时器时,就不断调整计时器在堆中的位置保证堆顶是一个最小计时。当计时器从堆中删除时就不断的向下不断找最小的计时器放在堆顶,至于最小堆的实现这里有一篇不错的文章分享给大家 : 二叉堆(一)之 图文解析 和 C语言的实现
我这里给出一个实例代码:
#include <time.h> typedef struct {int persist; //是否坚持time_t timeout; //timeout = 时间间隔 + 现在时间time_t timegap; //时间间隔void * data; //用户数据 }Timer; typedef struct {Timer *timer;size_t size;size_t capacity; }HeapTimer; //动态数组 最小堆HeapTimer * initHeapTimer(size_t s); void min_heap_push(HeapTimer * ht, Timer * _timer); void min_heap_pop(HeapTimer * ht, Timer * _timer); int check_had_timeout(HeapTimer *ht, time_t now);
#include <stdlib.h> #include <stdio.h> #include <string.h> #include "heap_time.h" //添加计时器后 需要不断上调二茶堆 保证堆顶最小 static void flow_up(HeapTimer *ht, Timer *_timer) {if (ht && _timer){if (ht->size == 0){return;}else if (ht->size == 1){memcpy(&(ht->timer[ht->size]), _timer, sizeof(Timer));_timer = NULL;return;}int temp = ht->size;while(temp){ //与父节点比较if (ht->timer[temp/2].timeout > (_timer->timeout)){ht->timer[temp].timeout = ht->timer[temp/2].timeout;ht->timer[temp].data = ht->timer[temp/2].data;ht->timer[temp].persist = ht->timer[temp/2].persist;ht->timer[temp].timegap = ht->timer[temp/2].timegap;}else{break;}temp = temp/2;}memcpy(&(ht->timer[temp]), _timer, sizeof(Timer));_timer = NULL;} } //删除堆顶不断调整 static void flow_down(HeapTimer *ht) {unsigned int start = 1;unsigned int end = ht->size;unsigned int current = start;unsigned int l = 2*current;while(l <= end){ //当节点存在右孩子,比较左右两个孩子找到最小的一个if (l < end && (ht->timer[l+1].timeout < ht->timer[l].timeout)){l++;} //拿最后一个与上次比较最小的一个比较 如果比它小就停止将最后一个放到此位置if (ht->timer[end].timeout < ht->timer[l].timeout){break;}else{ //将最小的上浮继续比对ht->timer[current].timeout = ht->timer[l].timeout;ht->timer[current].data = ht->timer[l].data;ht->timer[current].persist = ht->timer[l].persist;ht->timer[current].timegap = ht->timer[l].timegap;current = l;l = 2*current;}}ht->timer[current].timeout = ht->timer[end].timeout;ht->timer[current].data = ht->timer[end].data;ht->timer[current].persist = ht->timer[end].persist;ht->timer[current].timegap = ht->timer[end].timegap; }HeapTimer * initHeapTimer(size_t s) {if (s <= (size_t)0){return NULL;}HeapTimer * ht = (HeapTimer *)malloc(sizeof(HeapTimer));if (!ht){return NULL;}ht->size = 0;ht->capacity = s+1;ht->timer = (Timer *)malloc(sizeof(Timer) * (s+1));if (ht->timer){memset(ht->timer, 0, sizeof(Timer)*(s+1));return ht;}return NULL; } //插入一个定时器 void min_heap_push(HeapTimer * ht, Timer * _timer) {if (ht && _timer){if (ht->size == ht->capacity){size_t need = ht->size + (ht->size)/3;Timer *temp = (Timer *)malloc(sizeof(Timer)*(need));if(temp){memset(temp, 0, sizeof(Timer)*(need));memcpy(temp, ht->timer, sizeof(ht->size));ht->capacity = need;free(ht->timer);ht->timer = temp;temp = NULL;}}(ht->size)++;flow_up(ht, _timer);} } //删除一个定时器 void min_heap_pop(HeapTimer * ht, Timer * _timer) {if ( ht && ht->size){memset(_timer, 0, sizeof(Timer));memcpy(_timer, &(ht->timer[1]), sizeof(Timer));ht->timer[1].timeout = ht->timer[ht->size].timeout;ht->timer[1].data = ht->timer[ht->size].data;ht->timer[1].persist = ht->timer[ht->size].persist;ht->timer[1].timegap = ht->timer[ht->size].timegap;if (ht->size > 1){flow_down(ht);}ht->timer[ht->size].timeout = 0;ht->timer[ht->size].data = NULL;ht->timer[ht->size].persist = 0;ht->timer[ht->size].timegap = 0;(ht->size)--;} } //检查是否超时 int check_had_timeout(HeapTimer *ht, time_t now) {if (ht){if (ht->size > (size_t)0){return (ht->timer[1]).timeout < now;}}return 0; } //为测试使用 int main() {HeapTimer *timer = initHeapTimer(50);Timer t1;t1.timegap = 3;t1.timeout = time(NULL) + t1.timegap;t1.persist = 1;Timer t2;t2.timegap = 6;t2.timeout = time(NULL) + 6;t2.persist = 0;min_heap_push(timer, &t1);min_heap_push(timer, &t2);while(1){if (check_had_timeout(timer, time(NULL))) //不断检查和调整{Timer temp;min_heap_pop(timer, &temp); //if (temp.persist){temp.timeout = time(NULL) + temp.timegap;min_heap_push(timer, &temp);}printf("timeout %u\n", timer->size);}usleep(100);}return 0; }
堆计时器的效率还是可以的,插入删除logN。目前在网络编程中 最小堆计时常用到。
总结:
综上所述这三种常用的计时器做了简单介绍,其中实例代码仍有许多改进之处,存在不足之处敬请见谅及反馈,可以在github上找到: https://github.com/BambooAce/MyEvent/tree/master/src比较这三种定时器,一般在网络编程中较推荐的是链表计时器和最小堆计时器,在最新的libevent中也同时提供了这两种计时器可供选择。在网络编程中不乏缺少心跳检测,当客户端数量十分庞大时,小根堆定时器的好处就很明显了。
转载于:https://www.cnblogs.com/MaAce/p/8005723.html
Linux 编程--三种常用的定时器相关推荐
- 嵌入式linux内核oops,Linux编程时遇到Oops提示该如何排查?
各位工程师在Linux下开发程序时,有没有遇到由于系统中存在某些小故障而跳出了"Oops"提示的情况,此时你是如何排查故障?一行行的查看代码吗?其实不用那么复杂,本文将为你介绍一种 ...
- Linux运行项目部分空指针,技术文章—Linux编程时遇到Oops提示该如何排查?
各位工程师在Linux下开发程序时,有没有遇到由于系统中存在某些小故障而跳出了"Oops"提示的情况,此时你是如何排查故障?一行行的查看代码吗?其实不用那么复杂,本文将为你介绍一种 ...
- 牛人整理分享的面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结...
网站地址:http://www.itmian4.com 基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. ...
- Linux下的微秒级定时器: usleep, nanosleep, select, pselect
Linux下的微秒级定时器: usleep, nanosleep, select, pselect 标签: linuxnulldelaystructdate 2012-02-07 23:29 4979 ...
- 计算机基础(八):linux编程规范总结
linux编程规范小结 1.函数:(子)模块接口函数大写字母开头: AddUser() (子)模块内部调用函数小写字母开头:addUser() 接口函数/回调函数以' ...
- Linux编程技术总结
前言 所谓Linux编程技术就是在Linux操作系统环境下进行软件开发时所使用的各种编程技术. 一 常见的Linux系统发行版 在介绍常见的 Linux 系统版本之前,首先需要区分 Linux 系统内 ...
- Linux时间子系统之高分辨率定时器层(HR Timer)
在前面介绍定时器层的文章中我们已经知道了在Linux内核中已经存在了一个管理定时器的通用框架.不过它也有很多不足,最大的问题是其精度不是很高.哪怕底层的定时事件设备精度再高,定时器层的分辨率只能达到T ...
- linux内核oops消息分析,Linux 编程时遇到 Oops 提示该如何排查?
原标题:Linux 编程时遇到 Oops 提示该如何排查? 各位工程师在Linux下开发程序时,有没有遇到由于系统中存在某些小故障而跳出了"Oops"提示的情况,此时你是如何排查故 ...
- Linux编程题:信号量同步三进程依次打印若干次ABC
三个进程依次打印ABC..... 思路及大致流程如下: 思路取自这位大哥: Linux编程题:创建3个线程分别打印abc,用信号量进行同步_cleverlemon的博客-CSDN博客 这位大哥写的是线 ...
最新文章
- LNOI2014 LCA
- rabbitmq_management简介
- php入门02-数组
- C++ POD(Plain Old Data)类型
- 关于SVG文件在Firefox中正确显示的研究
- 这周,我们作前端,实现统一的过滤搜索
- 服务器虚拟光驱无法加载,Proxmox/创建PVE/安装windows 2012r2系统无法识别硬盘/如何添加virtio驱动/...
- openlayers入门开发系列之热力图篇
- 蜻蜓fm收音机电脑版_追寻逝去的时光:Tivoli Audio M1BT收音机蓝牙音箱体验
- 如何理解 scanf(%*s,str)?
- 没有工厂模式(工厂模式2)
- jdk安装好了怎么使用_安装jdk怎么打开
- rtmp协议 java_rtmp协议详解 (一) handshake
- andriod studio 运行项目时没有NDK(Android Studio如何更改JDK和SDK或者DNK的路径)
- Python sorted函数|sorted([13,1,237,89,100],key=lambda x:len(str(x)))
- java base64 加号_Base64 空格,加号问题
- 计算机sci转让,紧急转让的环境sci,一共四十个选题,2020年上半年出版,包SCI检索...
- win7如何启用计算机的无线功能,win7如何开启无线功能
- 如何获取彩色图像中的主色彩
- 国内foursquare类型网站 玩转四方 www.play4f.org
热门文章
- mysql 修改字段为1-10的随机数
- QT5界面操作3: QPainter和paintEvent(self, event)的配合
- excel 小知识汇总
- Python中的线程threading.Thread()使用详解
- java中正则全局匹配_JS中正则表达式全局匹配模式/g用法实例
- 计算机科学与应用论文的素材,计算机专业学位论文范文最新资料汇集
- 双向链表删除节点时间复杂度_删除链表的节点(剑指offer第十七题)
- python with open as yaml_python怎么解析yaml文件?
- 一篇超级详细的jsp+servlet+jdbc+mysql检验登录操作(IntelliJ IDEA)
- VS2010打开项目提示未能正确加载解决方案中一个或多个项目