时间轮 (Timing-Wheel) 算法类似于一以恒定速度旋转的左轮手枪,枪的撞针则撞击枪膛,如果枪膛中有子弹,则会被击发;与之相对应的是:对于 PerTickBookkeeping,其最本质的工作在于以 Tick 为单位增加时钟,如果发现有任何定时器到期,则调用相应的 ExpiryProcessing。设定一个循环为 N 个 Tick 单元,当前时间是在 S 个循环之后指向元素 i (i>=0 and i<= N - 1),则当前时间 (Current Time)Tc 可以表示为:Tc = S*N + i ;如果此时插入一个时间间隔 (Time Interval) 为 Ti 的定时器,设定它将会放入元素 n(Next) 中,则 n = (Tc + Ti)mod N = (S*N + i + Ti) mod N = (i + Ti) mod N 。如果我们的 N 足够的大,显然 StartTimer,StopTimer,PerTickBookkeeping 时,算法复杂度分别为 O(1),O(1),O(1) 。下图是一个简单的时间轮定时器:

如果需要支持的定时器范围非常的大,上面的实现方式则不能满足这样的需求。因为这样将消耗非常可观的内存,假设需要表示的定时器范围为:0 – 2^3-1ticks,则简单时间轮需要 2^32 个元素空间,这对于内存空间的使用将非常的庞大。也许可以降低定时器的精度,使得每个 Tick 表示的时间更长一些,但这样的代价是定时器的精度将大打折扣。现在的问题是,度量定时器的粒度,只能使用唯一粒度吗?想想日常生活中常遇到的水表,如下图 :

在上面的水表中,为了表示度量范围,分成了不同的单位,比如 1000,100,10 等等,相似的,表示一个 32bits 的范围,也不需要 2^32 个元素的数组。实际上,Linux 的内核把定时器分为 5 组,每组的粒度(对应水表例子的单位)分别表示为:1 jiffies,256 jiffies,256*64 jiffies,256*64*64 jiffies,256*64*64*64 jiffies,每组中桶的数量分别为:256,64,64,64,64,能表示的范围为 2^32 。有了这样的实现,驱动内核定时器的机制也可以通过水表的例子来理解了,就像水表,每个粒度上都有一个指针指向当前时间,时间以固定 tick 递增,而当前时间指针则也依次递增,如果发现当前指针的位置可以确定为一个注册的定时器,就触发其注册的回调函数。 Linux 内核定时器本质上是 Single-Shot Timer,如果想成为 Repeating Timer,可以在注册的回调函数中再次的注册自己。以下是实现代码:

// def.h

#ifndef _DEF_H_

#define _DEF_H_

#if defined(_WIN32) || defined(_WIN64)

#define STDCALL__stdcall

#else

#define STDCALL __attribute__((stdcall))

#endif

//基本数据类型定义

typedef char int8 ;

typedef unsigned char uint8 ;

typedef uint8 byte ;

typedef short int16 ;

typedef unsigned short uint16 ;

typedef long int32 ;

typedef unsigned long uint32 ;

#endif //_DEF_H_

// Lock.h 同步锁

#ifndef _LOCK_H_

#define _LOCK_H_

#if defined(_WIN32) || defined(_WIN64)

#include

typedef CRITICAL_SECTION LOCK ;

#else

#include

typedef pthread_mutex_t LOCK ;

#endif

void InitLock(LOCK *pLock) ;

void UninitLock(LOCK *pLock) ;

void Lock(LOCK *pLock) ;

void Unlock(LOCK *pLock) ;

#endif //_LOCK_H_

// Lock.c 同步锁

#include "Lock.h"

void InitLock(LOCK *pLock)

{

#if defined(_WIN32) || defined(_WIN64)

InitializeCriticalSection(pLock) ;

#else

pthread_mutex_init(pLock, NULL);

#endif

}

void UninitLock(LOCK *pLock)

{

#if defined(_WIN32) || defined(_WIN64)

DeleteCriticalSection(pLock) ;

#else

pthread_mutex_destroy(pLock);

#endif

}

void Lock(LOCK *pLock)

{

#if defined(_WIN32) || defined(_WIN64)

EnterCriticalSection(pLock) ;

#else

pthread_mutex_lock(pLock);

#endif

}

void Unlock(LOCK *pLock)

{

#if defined(_WIN32) || defined(_WIN64)

LeaveCriticalSection(pLock) ;

#else

pthread_mutex_unlock(pLock);

#endif

}

// Thread.h 定时器调度线程

#ifndef _THREAD_H_

#define _THREAD_H_

#if defined(_WIN32) || defined(_WIN64)

#include

#include //_beginthreadex()

typedef HANDLE THREAD ;

#else

#include

typedef pthread_t THREAD ;

#endif

#include "def.h"

typedef void* (*FNTHREAD)(void *pParam);

THREAD ThreadCreate(FNTHREAD fnThreadProc, void *pParam) ;

void ThreadJoin(THREAD thread) ;

void ThreadDestroy(THREAD thread) ;

#endif //_THREAD_H_

// Thread.c 定时器调度线程

#include "Thread.h"

THREAD ThreadCreate(FNTHREAD fnThreadProc, void *pParam)

{

#if defined(_WIN32) || defined(_WIN64)

if(fnThreadProc == NULL)

return NULL ;

return (THREAD)_beginthreadex(NULL, 0, (LPTHREAD_START_ROUTINE)fnThreadProc, pParam, 0, NULL) ;

#else

THREAD t ;

if(fnThreadProc == NULL)

return 0 ;

if(pthread_create(&t, NULL, fnThreadProc, pParam) == 0)

return t ;

else

return (THREAD)0 ;

#endif

}

void ThreadJoin(THREAD thread)

{

#if defined(_WIN32) || defined(_WIN64)

WaitForSingleObject(thread, INFINITE) ;

#else

pthread_join(thread, NULL) ;

#endif

}

void ThreadDestroy(THREAD thread)

{

#if defined(_WIN32) || defined(_WIN64)

CloseHandle(thread) ;

#else

//

#endif

}

// Timer.h

#ifndef _TIMER_H_

#define _TIMER_H_

#include "def.h"

#include "Lock.h"

#include "Thread.h"

#define CONFIG_BASE_SMALL 0 //TVN_SIZE=64 TVR_SIZE=256

#define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6)

#define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)

#define TVN_SIZE (1 << TVN_BITS)

#define TVR_SIZE (1 << TVR_BITS)

#define TVN_MASK (TVN_SIZE - 1)

#define TVR_MASK (TVR_SIZE - 1)

#define MAX_TVAL ((unsigned long)((1ULL << (TVR_BITS + 4*TVN_BITS)) - 1))

#define TIME_AFTER(a,b) ((long)(b) - (long)(a) < 0)

#define TIME_BEFORE(a,b) TIME_AFTER(b,a)

#define TIME_AFTER_EQ(a,b) ((long)(a) - (long)(b) >= 0)

#define TIME_BEFORE_EQ(a,b) TIME_AFTER_EQ(b,a)

typedef void (STDCALL *FNTIMRCALLBACK)(void *pParam) ;

typedef struct LIST_TIMER

{

struct LIST_TIMER *pPrev ;

struct LIST_TIMER *pNext ;

} LISTTIMER, *LPLISTTIMER ;

typedef struct TIMER_NODE

{

struct LIST_TIMER ltTimer ;//定时器链表的入口

uint32 uExpires ;//定时器超时的时刻

uint32 uPeriod ;//间隔多长时间触发一次

FNTIMRCALLBACK fnTimer ;//定时器处理函数

void *pParam ;//回调函数的参数

} TIMERNODE, *LPTIMERNODE ;

typedef struct TIMER_MANAGER

{

LOCK lock ;//同步锁

THREAD thread ;//线程句柄

uint32 uExitFlag ;//退出标识(0:Continue, other: Exit)

uint32 uJiffies ;//基准时间(当前时间)

struct LIST_TIMER arrListTimer1[TVR_SIZE] ;

struct LIST_TIMER arrListTimer2[TVN_SIZE] ;

struct LIST_TIMER arrListTimer3[TVN_SIZE] ;

struct LIST_TIMER arrListTimer4[TVN_SIZE] ;

struct LIST_TIMER arrListTimer5[TVN_SIZE] ;

} TIMERMANAGER, *LPTIMERMANAGER ;

void STDCALL SleepMilliseconds(uint32 uMs) ;

//创建定时器管理器

LPTIMERMANAGER STDCALL CreateTimerManager(void) ;

//删除定时器管理器

void STDCALL DestroyTimerManager(LPTIMERMANAGER lpTimerManager) ;

//创建一个定时器。fnTimer回调函数地址。pParam回调函数的参数。uDueTime首次触发的超时时间间隔。uPeriod定时器循环周期,若为0,则该定时器只运行一次。

LPTIMERNODE STDCALL CreateTimer(LPTIMERMANAGER lpTimerManager, FNTIMRCALLBACK fnTimer, void *pParam, uint32 uDueTime, uint32 uPeriod) ;

//删除定时器

int32 STDCALL DeleteTimer(LPTIMERMANAGER lpTimerManager, LPTIMERNODE lpTimer) ;

#endif //_TIMER_H_

// Timer.c 定时器实现

#include

#include

#if defined(_WIN32) || defined(_WIN64)

#include

#else

#include

#endif

#include "Timer.h"

//获取基准时间。

static uint32 GetJiffies_old(void)

{

#if defined(_WIN32) || defined(_WIN64)

SYSTEMTIME st ;

struct tm t ;

GetLocalTime(&st);

t.tm_year = st.wYear - 1900;

t.tm_mon = st.wMonth - 1;

t.tm_mday = st.wDay;

t.tm_hour = st.wHour;

t.tm_min = st.wMinute;

t.tm_sec = st.wSecond;

return (uint32)mktime(&t) * 1000 + st.wMilliseconds ;

#else

struct timeval tv ;

gettimeofday(&tv, NULL) ;

return tv.tv_sec * 1000 + tv.tv_usec / 1000 ;

#endif

}

static uint32 GetJiffies(void)

{

#if defined(_WIN32) || defined(_WIN64)

return GetTickCount() ;

#else

//需连接 rt库,加-lrt参数

struct timespec ts;

clock_gettime(CLOCK_MONOTONIC, &ts);

return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);

#endif

}

static void ListTimerInsert(struct LIST_TIMER *pNew, struct LIST_TIMER *pPrev, struct LIST_TIMER *pNext)

{

pNext->pPrev = pNew;

pNew->pNext = pNext;

pNew->pPrev = pPrev;

pPrev->pNext = pNew;

}

static void ListTimerInsertHead(struct LIST_TIMER *pNew, struct LIST_TIMER *pHead)

{

ListTimerInsert(pNew, pHead, pHead->pNext);

}

static void ListTimerInsertTail(struct LIST_TIMER *pNew, struct LIST_TIMER *pHead)

{

ListTimerInsert(pNew, pHead->pPrev, pHead);

}

static void ListTimerReplace(struct LIST_TIMER *pOld, struct LIST_TIMER *pNew)

{

pNew->pNext = pOld->pNext ;

pNew->pNext->pPrev = pNew ;

pNew->pPrev = pOld->pPrev ;

pNew->pPrev->pNext = pNew ;

}

static void ListTimerReplaceInit(struct LIST_TIMER *pOld, struct LIST_TIMER *pNew)

{

ListTimerReplace(pOld, pNew) ;

pOld->pNext = pOld ;

pOld->pPrev = pOld ;

}

static void InitArrayListTimer(struct LIST_TIMER *arrListTimer, uint32 nSize)

{

uint32 i ;

for(i=0; i

{

arrListTimer[i].pPrev = &arrListTimer[i] ;

arrListTimer[i].pNext = &arrListTimer[i] ;

}

}

static void DeleteArrayListTimer(struct LIST_TIMER *arrListTimer, uint32 uSize)

{

struct LIST_TIMER listTmr, *pListTimer ;

struct TIMER_NODE *pTmr ;

uint32 idx ;

for(idx=0; idx

{

ListTimerReplaceInit(&arrListTimer[idx], &listTmr) ;

pListTimer = listTmr.pNext ;

while(pListTimer != &listTmr)

{

pTmr = (struct TIMER_NODE *)((uint8 *)pListTimer - offsetof(struct TIMER_NODE, ltTimer)) ;

pListTimer = pListTimer->pNext ;

free(pTmr) ;

}

}

}

static void AddTimer(LPTIMERMANAGER lpTimerManager, LPTIMERNODE pTmr)

{

struct LIST_TIMER *pHead ;

uint32 i, uDueTime, uExpires ;

//

uExpires = pTmr->uExpires ; //定时器到期的时刻

uDueTime = uExpires - lpTimerManager->uJiffies ;

if (uDueTime < TVR_SIZE) //idx < 256 (2的8次方)

{

i = uExpires & TVR_MASK; //expires & 255

pHead = &lpTimerManager->arrListTimer1[i] ;

}

else if (uDueTime < 1 << (TVR_BITS + TVN_BITS)) //idx < 16384 (2的14次方)

{

i = (uExpires >> TVR_BITS) & TVN_MASK; // i = (expires>>8) & 63

pHead = &lpTimerManager->arrListTimer2[i] ;

}

else if (uDueTime < 1 << (TVR_BITS + 2 * TVN_BITS)) // idx < 1048576 (2的20次方)

{

i = (uExpires >> (TVR_BITS + TVN_BITS)) & TVN_MASK; // i = (expires>>14) & 63

pHead = &lpTimerManager->arrListTimer3[i] ;

}

else if (uDueTime < 1 << (TVR_BITS + 3 * TVN_BITS)) // idx < 67108864 (2的26次方)

{

i = (uExpires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK; // i = (expires>>20) & 63

pHead = &lpTimerManager->arrListTimer4[i] ;

}

else if ((signed long) uDueTime < 0)

{

/*

* Can happen if you add a timer with expires == jiffies,

* or you set a timer to go off in the past

*/

pHead = &lpTimerManager->arrListTimer1[(lpTimerManager->uJiffies & TVR_MASK)];

}

else

{

/* If the timeout is larger than 0xffffffff on 64-bit

* architectures then we use the maximum timeout:

*/

if (uDueTime > 0xffffffffUL)

{

uDueTime = 0xffffffffUL;

uExpires = uDueTime + lpTimerManager->uJiffies;

}

i = (uExpires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK; // i = (expires>>26) & 63

pHead = &lpTimerManager->arrListTimer5[i];

}

ListTimerInsertTail(&pTmr->ltTimer, pHead) ;

}

static uint32 CascadeTimer(LPTIMERMANAGER lpTimerManager, struct LIST_TIMER *arrListTimer, uint32 idx)

{

struct LIST_TIMER listTmr, *pListTimer ;

struct TIMER_NODE *pTmr ;

ListTimerReplaceInit(&arrListTimer[idx], &listTmr) ;

pListTimer = listTmr.pNext ;

while(pListTimer != &listTmr)

{

pTmr = (struct TIMER_NODE *)((uint8 *)pListTimer - offsetof(struct TIMER_NODE, ltTimer)) ;

pListTimer = pListTimer->pNext ;

AddTimer(lpTimerManager, pTmr) ;

}

return idx ;

}

static void RunTimer(LPTIMERMANAGER lpTimerManager)

{

#define INDEX(N) ((lpTimerManager->uJiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)

uint32 idx, uJiffies ;

struct LIST_TIMER listTmrExpire, *pListTmrExpire ;

struct TIMER_NODE *pTmr ;

if(NULL == lpTimerManager)

return ;

uJiffies = GetJiffies() ;

Lock(&lpTimerManager->lock) ;

while(TIME_AFTER_EQ(uJiffies, lpTimerManager->uJiffies))

{

idx = lpTimerManager->uJiffies & TVR_MASK ;

if (!idx &&

(!CascadeTimer(lpTimerManager, lpTimerManager->arrListTimer2, INDEX(0))) &&

(!CascadeTimer(lpTimerManager, lpTimerManager->arrListTimer3, INDEX(1))) &&

!CascadeTimer(lpTimerManager, lpTimerManager->arrListTimer4, INDEX(2)))

CascadeTimer(lpTimerManager, lpTimerManager->arrListTimer5, INDEX(3));

//lpTimerManager->uJiffies++ ;

pListTmrExpire = &listTmrExpire ;

ListTimerReplaceInit(&lpTimerManager->arrListTimer1[idx], pListTmrExpire) ;

pListTmrExpire = pListTmrExpire->pNext ;

while(pListTmrExpire != &listTmrExpire)

{

pTmr = (struct TIMER_NODE *)((uint8 *)pListTmrExpire - offsetof(struct TIMER_NODE, ltTimer)) ;

pListTmrExpire = pListTmrExpire->pNext ;

pTmr->fnTimer(pTmr->pParam) ;

//

if(pTmr->uPeriod != 0)

{

pTmr->uExpires = lpTimerManager->uJiffies + pTmr->uPeriod ;

AddTimer(lpTimerManager, pTmr) ;

}

else free(pTmr) ;

}

lpTimerManager->uJiffies++ ;

}

Unlock(&lpTimerManager->lock) ;

}

static void *ThreadRunTimer(void *pParam)

{

LPTIMERMANAGER pTimerMgr ;

pTimerMgr = (LPTIMERMANAGER)pParam ;

if(pTimerMgr == NULL)

return NULL ;

while(!pTimerMgr->uExitFlag)

{

RunTimer(pTimerMgr) ;

SleepMilliseconds(1) ;

}

return NULL ;

}

void STDCALL SleepMilliseconds(uint32 uMs)

{

#if defined(_WIN32) || defined(_WIN64)

Sleep(uMs) ;

#else

struct timeval tv;

tv.tv_sec = 0 ;

tv.tv_usec = uMs * 1000 ;

select(0, NULL, NULL, NULL, &tv);

#endif

}

//创建定时器管理器

LPTIMERMANAGER STDCALL CreateTimerManager(void)

{

LPTIMERMANAGER lpTimerMgr = (LPTIMERMANAGER)malloc(sizeof(TIMERMANAGER)) ;

if(lpTimerMgr != NULL)

{

lpTimerMgr->thread = (THREAD)0 ;

lpTimerMgr->uExitFlag = 0 ;

InitLock(&lpTimerMgr->lock) ;

lpTimerMgr->uJiffies = GetJiffies() ;

InitArrayListTimer(lpTimerMgr->arrListTimer1, sizeof(lpTimerMgr->arrListTimer1)/sizeof(lpTimerMgr->arrListTimer1[0])) ;

InitArrayListTimer(lpTimerMgr->arrListTimer2, sizeof(lpTimerMgr->arrListTimer2)/sizeof(lpTimerMgr->arrListTimer2[0])) ;

InitArrayListTimer(lpTimerMgr->arrListTimer3, sizeof(lpTimerMgr->arrListTimer3)/sizeof(lpTimerMgr->arrListTimer3[0])) ;

InitArrayListTimer(lpTimerMgr->arrListTimer4, sizeof(lpTimerMgr->arrListTimer4)/sizeof(lpTimerMgr->arrListTimer4[0])) ;

InitArrayListTimer(lpTimerMgr->arrListTimer5, sizeof(lpTimerMgr->arrListTimer5)/sizeof(lpTimerMgr->arrListTimer5[0])) ;

lpTimerMgr->thread = ThreadCreate(ThreadRunTimer, lpTimerMgr) ;

}

return lpTimerMgr ;

}

//删除定时器管理器

void STDCALL DestroyTimerManager(LPTIMERMANAGER lpTimerManager)

{

if(NULL == lpTimerManager)

return ;

lpTimerManager->uExitFlag = 1 ;

if((THREAD)0 != lpTimerManager->thread)

{

ThreadJoin(lpTimerManager->thread) ;

ThreadDestroy(lpTimerManager->thread) ;

lpTimerManager->thread = (THREAD)0 ;

}

DeleteArrayListTimer(lpTimerManager->arrListTimer1, sizeof(lpTimerManager->arrListTimer1)/sizeof(lpTimerManager->arrListTimer1[0])) ;

DeleteArrayListTimer(lpTimerManager->arrListTimer2, sizeof(lpTimerManager->arrListTimer2)/sizeof(lpTimerManager->arrListTimer2[0])) ;

DeleteArrayListTimer(lpTimerManager->arrListTimer3, sizeof(lpTimerManager->arrListTimer3)/sizeof(lpTimerManager->arrListTimer3[0])) ;

DeleteArrayListTimer(lpTimerManager->arrListTimer4, sizeof(lpTimerManager->arrListTimer4)/sizeof(lpTimerManager->arrListTimer4[0])) ;

DeleteArrayListTimer(lpTimerManager->arrListTimer5, sizeof(lpTimerManager->arrListTimer5)/sizeof(lpTimerManager->arrListTimer5[0])) ;

UninitLock(&lpTimerManager->lock) ;

free(lpTimerManager) ;

}

//创建一个定时器。fnTimer回调函数地址。pParam回调函数的参数。uDueTime首次触发的超时时间间隔。uPeriod定时器循环周期,若为0,则该定时器只运行一次。

LPTIMERNODE STDCALL CreateTimer(LPTIMERMANAGER lpTimerManager, FNTIMRCALLBACK fnTimer, void *pParam, uint32 uDueTime, uint32 uPeriod)

{

LPTIMERNODE pTmr = NULL ;

if(NULL == fnTimer || NULL == lpTimerManager)

return NULL ;

pTmr = (LPTIMERNODE)malloc(sizeof(TIMERNODE)) ;

if(pTmr != NULL)

{

pTmr->uPeriod = uPeriod ;

pTmr->fnTimer = fnTimer ;

pTmr->pParam = pParam ;

//

Lock(&lpTimerManager->lock) ;

pTmr->uExpires = lpTimerManager->uJiffies + uDueTime ;

AddTimer(lpTimerManager, pTmr) ;

Unlock(&lpTimerManager->lock) ;

}

return pTmr ;

}

//删除定时器

int32 STDCALL DeleteTimer(LPTIMERMANAGER lpTimerManager, LPTIMERNODE lpTimer)

{

struct LIST_TIMER *pListTmr ;

if(NULL != lpTimerManager && NULL != lpTimer)

{

Lock(&lpTimerManager->lock) ;

pListTmr = &lpTimer->ltTimer ;

pListTmr->pPrev->pNext = pListTmr->pNext ;

pListTmr->pNext->pPrev = pListTmr->pPrev ;

free(lpTimer) ;

Unlock(&lpTimerManager->lock) ;

return 0 ;

}

else

return -1 ;

}

// main.c 测试例子

#include

#include "Timer.h"

void STDCALL TimerFun(void *pParam)

{

LPTIMERMANAGER pMgr ;

pMgr = (LPTIMERMANAGER)pParam ;

printf("Timer expire! Jiffies: %lu\n", pMgr->uJiffies) ;

}

int main(void)

{

LPTIMERMANAGER pMgr ;

LPTIMERNODE pTn ;

pMgr = CreateTimerManager() ;

CreateTimer(pMgr, TimerFun, pMgr, 2000, 0) ;

pTn = CreateTimer(pMgr, TimerFun, pMgr, 4000, 1000) ;

SleepMilliseconds(10001) ;

DeleteTimer(pMgr, pTn) ;

SleepMilliseconds(3000) ;

DestroyTimerManager(pMgr) ;

return 0 ;

}

完整代码下载:百度云盘

时间轮python_时间轮定时器的实现(参考Linux源码)相关推荐

  1. HTML网页设计期末课程大作业 ~中国茶文化5页面带轮播(HTML+CSS+JS)~ 学生网页设计作业源码...

    HTML网页设计期末课程大作业 ~ 中国茶文化5页面带轮播(HTML+CSS+JS)~ 学生网页设计作业源码 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?HTML网页作业无从 ...

  2. HTML网页设计期末课程大作业 ~中国茶文化5页面带轮播(HTML+CSS+JS)~ 学生网页设计作业源码

    HTML网页设计期末课程大作业 ~ 中国茶文化5页面带轮播(HTML+CSS+JS)~ 学生网页设计作业源码 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?HTML网页作业无从 ...

  3. web网页设计——体育气步枪射击主题(5页面)带图片轮播特效(HTML+CSS) ~学生网页设计作业源码

    web网页设计--体育气步枪射击主题(5页面)带图片轮播特效(HTML+CSS) ~学生网页设计作业源码 临近期末, 你还在为HTML网页设计结课作业,老师的作业要求感到头大?HTML网页作业无从下手 ...

  4. Nginx/Openresty日志时间显示到毫秒级的三种方法(不改源码)

    Nginx/Openresty日志时间显示到毫秒级的三种方法(不改源码) 方法一 :set (只能在server节点内) 方法二: set_by_lua (只能在server节点内) 方法三: map ...

  5. 【TWVRP】基于matlab模拟退火算法求解带时间窗的车辆路径规划问题【含Matlab源码 160期】

    ⛄一.VRP简介 1 VRP基本原理 车辆路径规划问题(Vehicle Routing Problem,VRP)是运筹学里重要的研究问题之一.VRP关注有一个供货商与K个销售点的路径规划的情况,可以简 ...

  6. 【HTML+CSS+JS】模仿星巴克主页,练习CSS排版,JS实现轮播图以及悬停动画(公开完整源码)

    目录:完整源码仅供学习 一.效果演示 二.完整源代码 2.1 HTML代码 2.2 JS代码 2.3 CSS代码 2.3.1 index.css 2.3.2 base.css 三.完整项目下载 一.效 ...

  7. v35.03 鸿蒙内核源码分析(时间管理) | 内核基本时间单位是谁 | 百篇博客分析HarmonyOS源码

    子曰:"譬如为山,未成一篑,止,吾止也:譬如平地,虽覆一篑,进,吾往也." <论语>:子罕篇 百篇博客系列篇.本篇为: v35.xx 鸿蒙内核源码分析(时间管理篇) | ...

  8. 【TWVRP】基于matlab模拟退火算法结合狼群算法求解带时间窗的车辆路径规划问题【含Matlab源码 1075期】

    ⛄一.VRP简介 1 VRP基本原理 车辆路径规划问题(Vehicle Routing Problem,VRP)是运筹学里重要的研究问题之一.VRP关注有一个供货商与K个销售点的路径规划的情况,可以简 ...

  9. 【路径规划】基于遗传算法求解带时间窗车辆路径规划问题(VRPTW)matlab源码

    1 简介 有时间窗的车辆路径问题(Vehicle Routing Problem with Time Windows,VRPTW)因为其有重要的现实意义而备受关注.其时间窗即为客户接受服务的时间范围, ...

  10. linux 定时器_通过linux源码分析nodejs的keep-alive

    之前已经分析过了keep-alive,最近在使用nodejs的keep-alive的时候发现了遗漏了一个内容.本文进行一个补充说明.我们先看一下nodejs中keep-alive的使用. enable ...

最新文章

  1. AWS攻略——使用CodeCommit托管代码
  2. SNV分析软件/工具
  3. python科学计算基础教程pdf下载-Python科学计算基础教程_PDF电子书
  4. 【BZOJ1123】 [POI2008]BLO (tarjan)
  5. dump、load和dumps、loads的联系与区别
  6. 正向代理与反向代理的理解
  7. 什么网了解c语言,什么是c语言?
  8. 成功在家用ssh远程连上了学校电脑虚拟机当中的ubuntu(代价是虚拟机全部黑屏只能用SSH连接了!)
  9. 去掉源代码里的debug标致
  10. Mysql Alter table 操作
  11. 计算债券理论价格和久期,凸性_20200613_
  12. Airbnb房源信息爬取(二)——获取房源信息
  13. 初级软件测试工程师工资(薪资待遇)一般是多少?
  14. fpga卷积神经网络加速器,FPGA卷积神经网络综述
  15. iOS设计:绘图及性能
  16. 高内聚,低偶合指的是什么?
  17. 地统计学半方差分析几个名词含义说明
  18. 按头安利 好看又实用的公文包 文件包3d模型素材看这里
  19. 两款在线同义词典,告别乏味表达!
  20. 一文搞懂最强裂变团队的黑科技

热门文章

  1. Java - 生成健康证图片,各种模板图片
  2. 量化选股常见的策略模型
  3. 金格集成(springboot+vue)
  4. 用ie浏览器签章后保存在桌面显示不出文件
  5. 耐用的蓝牙耳机推荐,现在比较好的蓝牙耳机点评
  6. 声艺数字调音台si说明书32路_Soundcraft 声艺 Si Impact 数字调音台 32路数字调音台...
  7. 五笔:25 个一级简码(口诀)
  8. 【个人学习记录】RoboWare Studio安装使用
  9. python网易云音乐爬虫歌词_利用Python网络爬虫抓取网易云音乐歌词
  10. stm32c语言写数码管定时器,【程序】STM32F103单片机使用定时器DMA进行全自动8位数码管动态扫描...