引言

  今天我们要讲述和构建的是一个跨平台多线程C的定时器对象,粒度是毫秒级别.可以称之为简易的timer, sctimer.h 库.

首先看总的接口,看门见客. sctimer.h

#ifndef _H_SCTIMER
#define _H_SCTIMER #include <schead.h> /*
* 简单的定时器代码.跨平台,线程安全
*关键是使用简单.
*例如
* 1. 启动一次,不要求多线程, 1s后执行
* st_add(1, 1, 0, timer, arg, 0)
* 2. 启动轮询事件, 要求多线程,立即启动,并且每隔200ms执行一次
* st_add(0, -1, 200, timer, arg, 1)
*
*这些参数具体含义,讲述的很清楚. 你看明白后再用.或者把你常用的封装好
*/ /*
* 添加定时器事件,虽然设置的属性有点多但是都是必要的 .
* start : 延迟启动的时间, 0表示立即启动, 单位是毫秒
* cut : 表示执行次数, 0表示永久时间, 一次就为1
* intval : 每次执行的时间间隔, 单位是毫秒
* timer : 定时器执行函数
* arg : 定时器参数指针
* fb : 0表示不启用多线程, 1表示启用多线程
* : 返回这个定时器的 唯一id
*/
extern int st_add(int start, int cut, int intval, vdel_f timer, void* arg, bool fb); /*
* 删除指定事件
* st : st_add 返回的定时器id
*/
extern inline void st_del(int st); #endif // !_H_SCTIMER

基础数据结构确定 

不用慌. 这个应该是最简单的接口了.一个创建一个尝试取消接口. 很符合使用习惯. 这个部分不讨论代码细节.

简单认为 st_add 中 参数 timer理解为注册的事件器. schead.h 中提供一些跨平台使用的代码. 到这里那我们尽情的讨论设计.

使用过很多定时器库. 个人感觉 最爽的是

  .net framework 中提供的 Timer定时器. 真几把傻瓜化好用. 可惜 .net framework 效率不高,太依赖VS IDE并且Linux

平台上起步太晚. 这里可能扯偏了.

那继续 讨论定时器. 当我第一次考虑定时器的时候, 想到的的数据结构是 最小堆结构(自行Google). 当前最快执行的对象在堆顶,

后面就直接 sleep(min(t)) => run. 插入性能log级别, 执行性能是常量级别, 调整也是O(log). 最优了.

后来投入设计的时候发现,不说这种设计需要大量的交换, 关键在于加入 存在大量时间相同执行timer. 这种数据结构会大量交换.

因为普世性小环境,直接否了这种最小二叉堆结构.

  后面想到一种特殊堆结构, 升序链表 . 完全符合最小堆定义. 插入是O(n) 执行效率是 O(1),调整是O(1). 总的而言

也很好. 最后决定采用 升序链表结构. 那我们的数据结构 设计基本敲定了.

  (真希望, 有图, 图比语言更好理解. 希望有人盗链的时候帮我加上图吧.)

业务流程初步结构设计

  首先第一个业务是 st_add 有个参数是 cut,限定这个定时器timer执行的次数. cut == 0的时候表示永久循环的定时器.

我这里采用的算法思路是.

  0.在定时器链表中添加这个定时器对象, 将cut + 1, 塞入, 0的时候不动

  1.当执行这个定时器对象时候, 将其从定时器链表中弹出

  2.如果是 cut ==0 ,永久事件. 执行完毕后, 修改一下时间量,再 add进定时器链表中,再次轮序

  2.1 如果 cut > 1, 表示继续执行, 将cut-- 之后add进去

  3 .如果cut == 1的时候表示这个定时器对象可以 关闭了, 那么就释放.

业务模型多线程部分设计

这里需要处理一个问题, 定时器必须是异步的. 否则主线程就阻塞了. 我的思路是

  1.当我们st_add 第一次添加对象进去的时候. 开启 loop 函数一直轮序 定时器链表对象

  2.当我们st_add 添加对象 刚好是当前最小的对象, 对象 取消掉已经轮序的 线程. 重新构建关系再一次 开启新线程轮序

业务删除模块

  思路就是在定时器链表中查询,找到后直接弹出. 后面释放.

这里需要注意的是 上面三个模块都需要是互斥的. 就是需要用到锁. 我们这里使用的是原子锁.

好了到这里基本思路都说清楚了. 至少大致方向有了. 这里还有一个业务, 就是阻塞怎么设计. 我采用的是最优阻塞, 缺点是需要取消

重建.还有一种思路是小单位阻塞, 大量轮序.就看取舍了.

  思路比代码重要. 只有思路清晰了,装逼才容易. 会说的比会做的,感觉更吊.留下的都是思想家.没听说过行动家.

好那我们开始行动吧.

前言

  这里会简单的分析一些实现细节.

跨平台部分

首先看阻塞部分

/*
* 2.0 如果定义了 __GNUC__ 就假定是 使用gcc 编译器,为Linux平台
* 否则 认为是 Window 平台,不可否认宏是丑陋的
*/
#if defined(__GNUC__)
//下面是依赖 Linux 实现,等待毫秒数
#include <unistd.h>
#include <sys/time.h>
#define SLEEPMS(m) \
usleep(m * )
#else
// 这里创建等待函数 以毫秒为单位 , 需要依赖操作系统实现
#include <Windows.h>
#include <direct.h> // 加载多余的头文件在 编译阶段会去掉
#define rmdir _rmdir /**
* Linux sys/time.h 中获取时间函数在Windows上一种移植实现
**tv : 返回结果包含秒数和微秒数
**tz : 包含的时区,在window上这个变量没有用不返回
** : 默认返回0
**/
extern int gettimeofday(struct timeval* tv, void* tz); //为了解决 不通用功能
#define localtime_r(t, tm) localtime_s(tm, t) #define SLEEPMS(m) \
Sleep(m)
#endif /*__GNUC__ 跨平台的代码都很丑陋 */

主要看 SLEEPMS宏, linux上采用usleep 停顿微妙级别. window上使用Sleep 停顿毫秒级别. 这里就没事了.

获取时间部分

对于gettimerofday 这个函数linux上提供了, window上没有, 它返回时间单位. window实现如下

#if defined(_MSC_VER)
/**
* Linux sys/time.h 中获取时间函数在Windows上一种移植实现
**tv : 返回结果包含秒数和微秒数
**tz : 包含的时区,在window上这个变量没有用不返回
** : 默认返回0
**/
int
gettimeofday(struct timeval* tv, void* tz)
{
time_t clock;
struct tm tm;
SYSTEMTIME wtm; GetLocalTime(&wtm);
tm.tm_year = wtm.wYear - ;
tm.tm_mon = wtm.wMonth - ; //window的计数更好写
tm.tm_mday = wtm.wDay;
tm.tm_hour = wtm.wHour;
tm.tm_min = wtm.wMinute;
tm.tm_sec = wtm.wSecond;
tm.tm_isdst = -; //不考虑夏令时
clock = mktime(&tm);
tv->tv_sec = (long)clock; //32位使用,接口已经老了
tv->tv_usec = wtm.wMilliseconds * ; return _RT_OK;
}
#endif

利用GetLocalTime 实现的.比较粗暴. 这里扯一点, 关于schead.h 是simple c 开源基础框架中一个基本头文件. 最近优化了一处判断系统大小端代码如下

//12.0 判断是大端序还是小端序,大端序返回true
bool
sh_isbig(void)
{
static union {
unsigned short _s;
unsigned char _c;
} __u = { };
return __u._c == ;
}

更清爽了一点.以前是 unsigned char _cs[sizeof(unsigned short)]; 结构. 这里少了几个字符.快了一点.

线程库仍然是采用ptrhead 通用库.

通用的原子锁 scatom.h

这里展示gcc 部分提供的原子锁 代码吧

/*
加锁等待,知道 ATOM_SET 返回合适的值
_INT_USLEEP 是操作系统等待纳秒数,可以优化,看具体操作系统 使用方式
int lock = 0;
ATOM_LOCK(lock); //to do think ... ATOM_UNLOCK(lock); */
#define _INT_USLEEP (2)
#define ATOM_LOCK(v) \
while(ATOM_SET(v, )) \
usleep(_INT_USLEEP) /*
对ATOM_LOCK 解锁, 当然 直接调用相当于 v = 0;
*/
#define ATOM_UNLOCK(v) \
__sync_lock_release(&(v))

到这里基本跨平台部分准备的前戏基本就完成了. 后面最后测试的时候会展示所有的代码.

正文

  到这里会完整的展示代码和测试demo.

那我们开始讲代码吧,首先给全部. 学习日本, 虽然无趣, 但脱光再说. sctimer.c

#include <sctimer.h>
#include <scatom.h>
#include <pthread.h> // 使用到的定时器结点
struct stnode {
int id; //当前定时器的id
time_t stime; //运行的具体时间到秒
int ms; //还需要等待的毫秒数
int cut; //循环执行次数, -1表示一直执行
int intval; //下一次轮询的时间间隔
int type; //0表示不开启多线程, 1表示开启多线程
vdel_f timer; //执行的函数事件
void* arg; //执行函数参数
struct stnode* next; //下一个定时器结点
}; // 当前链表对象管理器
struct stlist {
int lock; //加锁用的
int nowid; //当前使用的最大timer id
int status; //0表示停止态, 1表示主线程loop运行态
pthread_t tid; //主循环线程id, 0表示没有启动
struct stnode* head; //定时器链表的头结点
}; // 定时器对象的单例, 最简就是最复杂
static struct stlist _st; // 先创建链表对象处理函数
static struct stnode* _new_stnode(int start, int cut, int intval, vdel_f timer, void* arg, bool fb)
{
int s = start / ;
struct stnode* node = malloc(sizeof(struct stnode));
if (NULL == node)
CERR_EXIT("_new_stnode malloc node is error!"); // 初始化, 首先初始化当前id
node->id = ATOM_ADD_FETCH(_st.nowid, );
node->stime = s + time(NULL);
node->ms = start - s*;
node->cut = cut > ? cut + : ; // 执行到1的时候停止,并且兼容永久时间0
node->intval = intval;
node->type = fb;
node->timer = timer;
node->arg = arg;
node->next = NULL; return node;
} // 如果stl < str 返回true, 否则返回false
static inline bool _stnode_cmp(struct stnode* stl, struct stnode* str)
{
return (stl->stime < str->stime) ||
(stl->stime == str->stime && stl->ms < str->ms);
} // 添加链表对象, 返回true表示插入的是头结点, 当你执行的时候需要全额加锁
static bool _stlist_add(struct stlist* st, struct stnode* node)
{
struct stnode* head; // 插入为头结点直接返回
if (!(head=st->head) || _stnode_cmp(node, head)) {
node->next = head;
st->head = node;
ATOM_UNLOCK(st->lock);
return true;
} // 中间插入了
while (head->next){
if (_stnode_cmp(node, head->next))
break;
head = head->next;
}
node->next = head->next;
head->next = node; return false;
} // 根据id,删除一个timer结点, 返回NULL表示没有找见不处理,多线程安全的
static struct stnode* _stlist_del(struct stlist* st, int id)
{
struct stnode *head, *tmp = NULL;
if (!(head = st->head)) return NULL; ATOM_LOCK(st->lock);
// 删除为头结点直接返回
if (head->id == id) {
st->head = head->next;
tmp = head;
}
else { // 中间删除那个结点了
while (head->next) {
if (head->next->id == id)
break;
head = head->next;
}
if (head->next) {
tmp = head->next;
head->next = tmp->next;
}
} ATOM_UNLOCK(st->lock);
return tmp;
} // 得到等待的时间,毫秒, <=0的时候头时间就可以执行了
static inline int _sleeptime(struct stlist* st)
{
struct stnode* head = st->head;
struct timeval tv;
gettimeofday(&tv, NULL);
return (int)(*(head->stime - tv.tv_sec) + head->ms - tv.tv_usec/);
} // timer线程执行的函数
static void* _slnode_timer(struct stnode* sn)
{
pthread_detach(pthread_self()); //设置线程分离,自销毁
sn->timer(sn->arg);
return NULL;
} //重新调整, 只能在 _stlist_loop 后面调用, 线程安全,只加了一把锁
static void _slnode_again_run(struct stlist* st)
{
int s, v;
pthread_t tid;
struct stnode* sn; ATOM_LOCK(st->lock); // 加锁防止调整关系覆盖,可用还是比较重要的
sn = st->head;
st->head = sn->next;
if (sn->cut == ){ //这时候不需要了,才开始删除
ATOM_UNLOCK(st->lock);
free(sn);
return;
} //这里需要重新组织数据
sn->cut = sn->cut ? sn->cut - : ;
s = sn->intval + sn->ms;
v = s / ;
sn->stime += v;
sn->ms = s - v*; if (sn->type) // 开始处理,先处理异步模式
pthread_create(&tid, NULL, (void* (*)(void*))_slnode_timer, sn);
else //同步模式
sn->timer(sn->arg);
_stlist_add(st, sn);
ATOM_UNLOCK(st->lock);
} // 运行的主loop,基于timer管理器
static void* _stlist_loop(struct stlist* st)
{
int nowt; //设置线程属性, 默认线程属性 允许退出线程
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); //设置立即取消
pthread_detach(pthread_self()); //设置线程分离,自销毁 // 正常轮询,检测时间
while (st->head) {
pthread_testcancel(); //添加测试取消点
nowt = _sleeptime(st);
if(nowt <= || st->head->cut == )
_slnode_again_run(st); //重新调整关系并且开始执行
else //没有人到这那就继续等待
SLEEPMS(nowt);
}
// 已经运行结束
st->status = ;
return NULL;
} /*
* 添加定时器事件,虽然设置的属性有点多但是都是必要的 .
* start : 延迟启动的时间, 0表示立即启动, 单位是毫秒
* cut : 表示执行次数, 0表示永久时间, 一次就为1
* intval : 每次执行的时间间隔, 单位是毫秒
* timer : 定时器执行函数
* arg : 定时器参数指针
* fb : 0表示不启用多线程, 1表示启用多线程
* : 返回这个定时器的 唯一id
*/
int
st_add(int start, int cut, int intval, vdel_f timer, void* arg, bool fb)
{
struct stnode* now;
DEBUG_CODE({
if(start< || cut< || intval< || !timer)
CERR_EXIT("debug start,cut,intval,timer => %d,%d,%d,%p.", start, cut, intval, timer);
});
// 这里开始创建对象往 线程队列中添加
now = _new_stnode(start, cut, intval, timer, arg, fb); ATOM_LOCK(_st.lock); //核心添加模块 要等, 添加到链表, 看线程能否取消等
_stlist_add(&_st, now);
// 看是否需要取消线程
if(_st.status == && _sleeptime(&_st) < ){
pthread_cancel(_st.tid);
_st.status = ;
}
// 这个时候重新开启线程
if(_st.status == ){
pthread_create(&_st.tid, NULL, (void* (*)(void*))_stlist_loop, &_st);
_st.status = ; //延迟真实运行态
}
ATOM_UNLOCK(_st.lock); return now->id;
} /*
* 删除指定事件, 删除是临时加上的存在临界的意外.
* st : st_add 返回的定时器id
*/
inline void
st_del(int st)
{
struct stnode* sn = _stlist_del(&_st, st);
if(sn) free(sn);
}

那我们开始解说吧,从数据结构说起. 先看每个 tiemr 结点结构

// 使用到的定时器结点
struct stnode {
int id; //当前定时器的id
time_t stime; //运行的具体时间到秒
int ms; //还需要等待的毫秒数
int cut; //循环执行次数, -1表示一直执行
int intval; //下一次轮询的时间间隔
int type; //0表示不开启多线程, 1表示开启多线程
vdel_f timer; //执行的函数事件
void* arg; //执行函数参数
struct stnode* next; //下一个定时器结点
};

仔细看看, 这些都是必须的. 链表是为了找到下一个结点. (上面//不对其是, 博客园和window上软件关于 \t 计算代码不一致造成的.)

再看timer管理器结构

// 当前链表对象管理器
struct stlist {
int lock; //加锁用的
int nowid; //当前使用的最大timer id
int status; //0表示停止态, 1表示主线程loop运行态
pthread_t tid; //主循环线程id, 0表示没有启动
struct stnode* head; //定时器链表的头结点
};

写的很详细, lock是加锁用的,每个链表一个锁. nowid 是为了记录当前已经用的timer对象. 假定用不完. 这个定时器个人代码估计, 定时器永久循环

对象破了2,3千基本就不行了. 需要重新开线程优化了. 当然了这种情况出现了不仅仅是结构优化就能解决了. 需要系统层优化了.

status标志当前是否有主loop线程在运行. 原本思路是通过pthread_t 判断.但是 不同平台pthread_t 实现不一样放弃了.

Linux 上 设计为unsigend long. 但是window上设计为

    /*
* Generic handle type - intended to extend uniqueness beyond
* that available with a simple pointer. It should scale for either
* IA-32 or IA-64.
*/
typedef struct {
void * p; /* Pointer to actual object */
unsigned int x; /* Extra information - reuse count etc */
} ptw32_handle_t;
typedef ptw32_handle_t pthread_t;

所以跨平台程序不要假定 pthread_t的实现方式.

说到这. 后面基本都是大白话. 最需要注意的是上面关于加锁部分. 这些内容是为了防止冲突,都对定时器链表修改导致数据意外.

关于 链表的插入和删除都是老套路, 多写多练习.  算法也许有点吹毛求疵.数据结构真的会用到.

多线程部分, 也不好搞,特别是调试部分. 没什么好方法,从同步开始, 慢慢来...

    //设置线程属性, 默认线程属性 允许退出线程
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); //设置立即取消
pthread_detach(pthread_self()); //设置线程分离,自销毁

上面代码意思,注释了. 第一个是为了, 在pthread_cancel的时候能够起到效果. 后面是为了分离. 让其销毁时候不再保留对象等待pthread_join来回收.

总的而言多线程编程门道很多. 水很深. 很多选手也就会create 一下, 当然我自己也是. 有机会单独写个专题深入讲解多线程开发.或多线程业务代码剖析.

到这里,基本上讲解完了. 代码短不好理解. 就当看看吧, 了解一种思路总是好的. (可能上面代码中也存在错误,以后再更正吧).

先讲讲linux 上测试结果. 测试代码 test_sctimer.c

#include <sctimer.h>

static int _sm;

static void _timer(void* arg)
{
char tstr[];
sh_times(tstr, LEN(tstr));
printf("%p + %d => %s\n", arg, ++_sm, tstr);
} int main(int argc, char* argv[])
{
st_add(, , , _timer, (void*), false);
st_add(, , , _timer, (void*), false);
st_add(, , , _timer, (void*), false); // 开启一个多线程的永久异步方法
int tid = st_add(, , , _timer, (void*), true); // 等待5秒后关闭 上面永久的定时器事件
SLEEPMS();
st_del(tid); // 再注册一个方法
st_add(, , , _timer, (void*), false); sh_pause();
return ;
}

运行的测试结果如下

基本上能跑起来.

那好吧. 我贴上 用到的其它代码图.

schead.h

#ifndef _H_SCHEAD
#define _H_SCHEAD #include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <stddef.h> /*
* 1.0 错误定义宏 用于判断返回值状态的状态码 _RF表示返回标志
* 使用举例 :
int flag = scconf_get("pursue");
if(flag != _RT_OK){
sclog_error("get config %s error! flag = %d.", "pursue", flag);
exit(EXIT_FAILURE);
}
* 这里是内部 使用的通用返回值 标志
*/
#define _RT_OK (0) //结果正确的返回宏
#define _RT_EB (-1) //错误基类型,所有错误都可用它,在不清楚的情况下
#define _RT_EP (-2) //参数错误
#define _RT_EM (-3) //内存分配错误
#define _RT_EC (-4) //文件已经读取完毕或表示链接关闭
#define _RT_EF (-5) //文件打开失败 /*
* 1.1 定义一些 通用的函数指针帮助,主要用于基库的封装中
* 有构造函数, 释放函数, 比较函数等
*/
typedef void* (*pnew_f)();
typedef void (*vdel_f)(void* node);
// icmp_f 最好 是 int cmp(const void* ln,const void* rn); 标准结构
typedef int (*icmp_f)(); /*
* c 如果是空白字符返回 true, 否则返回false
* c : 必须是 int 值,最好是 char 范围
*/
#define sh_isspace(c) \
((c==' ')||(c>='\t'&&c<='\r')) /*
* 2.0 如果定义了 __GNUC__ 就假定是 使用gcc 编译器,为Linux平台
* 否则 认为是 Window 平台,不可否认宏是丑陋的
*/
#if defined(__GNUC__)
//下面是依赖 Linux 实现,等待毫秒数
#include <unistd.h>
#include <sys/time.h>
#define SLEEPMS(m) \
usleep(m * )
#else
// 这里创建等待函数 以毫秒为单位 , 需要依赖操作系统实现
#include <Windows.h>
#include <direct.h> // 加载多余的头文件在 编译阶段会去掉
#define rmdir _rmdir /**
* Linux sys/time.h 中获取时间函数在Windows上一种移植实现
**tv : 返回结果包含秒数和微秒数
**tz : 包含的时区,在window上这个变量没有用不返回
** : 默认返回0
**/
extern int gettimeofday(struct timeval* tv, void* tz); //为了解决 不通用功能
#define localtime_r(t, tm) localtime_s(tm, t) #define SLEEPMS(m) \
Sleep(m)
#endif /*__GNUC__ 跨平台的代码都很丑陋 */ //3.0 浮点数据判断宏帮助, __开头表示不希望你使用的宏
#define __DIFF(x, y) ((x)-(y)) //两个表达式做差宏
#define __IF_X(x, z) ((x)<z&&(x)>-z) //判断宏,z必须是宏常量
#define EQ(x, y, c) EQ_ZERO(__DIFF(x,y), c) //判断x和y是否在误差范围内相等 //3.1 float判断定义的宏
#define _FLOAT_ZERO (0.000001f) //float 0的误差判断值
#define EQ_FLOAT_ZERO(x) __IF_X(x,_FLOAT_ZERO) //float 判断x是否为零是返回true
#define EQ_FLOAT(x, y) EQ(x, y, _FLOAT_ZERO) //判断表达式x与y是否相等 //3.2 double判断定义的宏
#define _DOUBLE_ZERO (0.000000000001) //double 0误差判断值
#define EQ_DOUBLE_ZERO(x) __IF_X(x,_DOUBLE_ZERO) //double 判断x是否为零是返回true
#define EQ_DOUBLE(x,y) EQ(x, y, _DOUBLE_ZERO) //判断表达式x与y是否相等 //4.0 控制台打印错误信息, fmt必须是双引号括起来的宏
#ifndef CERR
#define CERR(fmt, ...) \
fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)
#endif/* !CERR */ //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
#ifndef CERR_EXIT
#define CERR_EXIT(fmt,...) \
CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
#endif/* !ERR */ #ifndef IF_CERR
/*
*4.2 if 的 代码检测
*
* 举例:
* IF_CERR(fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), "socket create error!");
* 遇到问题打印日志直接退出,可以认为是一种简单模板
* code : 要检测的代码
* fmt : 必须是""括起来的字符串宏
* ... : 后面的参数,参照printf
*/
#define IF_CERR(code, fmt, ...) \
if((code) < ) \
CERR_EXIT(fmt, ##__VA_ARGS__)
#endif //!IF_CERR #ifndef IF_CHECK
/*
* 是上面IF_CERR 的简化版很好用
*/
#define IF_CHECK(code) \
if((code) < ) \
CERR_EXIT(#code)
#endif // !IF_CHECK //5.0 获取数组长度,只能是数组类型或""字符串常量,后者包含'\0'
#ifndef LEN
#define LEN(arr) \
(sizeof(arr)/sizeof(*(arr)))
#endif/* !ARRLEN */ //6.0 程序清空屏幕函数
#ifndef CONSOLE_CLEAR
#ifndef _WIN32
#define CONSOLE_CLEAR() \
system("printf '\ec'")
#else
#define CONSOLE_CLEAR() \
system("cls")
#endif/* _WIN32 */
#endif /*!CONSOLE_CLEAR*/ //7.0 置空操作
#ifndef BZERO
//v必须是个变量
#define BZERO(v) \
memset(&v,,sizeof(v))
#endif/* !BZERO */ //9.0 scanf 健壮的
#ifndef SAFETY_SCANF
#define SAFETY_SCANF(scanf_code,...) \
while(printf(__VA_ARGS__),scanf_code){\
while(getchar()!='\n');\
puts("输入出错,请按照提示重新操作!");\
}\
while(getchar()!='\n')
#endif /*!SAFETY_SCANF*/ //10.0 简单的time帮助宏
#ifndef TIME_PRINT
#define TIME_PRINT(code) {\
clock_t __st,__et;\
__st=clock();\
code\
__et=clock();\
printf("当前代码块运行时间是:%lf秒\n",(0.0+__et-__st)/CLOCKS_PER_SEC);\
}
#endif /*!TIME_PRINT*/ /*
* 10.1 这里是一个 在 DEBUG 模式下的测试宏
*
* 用法 :
* DEBUG_CODE({
* puts("debug start...");
* });
*/
#ifndef DEBUG_CODE
# ifdef _DEBUG
# define DEBUG_CODE(code) code
# else
# define DEBUG_CODE(code)
# endif // ! _DEBUG
#endif // !DEBUG_CODE //11.0 等待的宏 是个单线程没有加锁
#define _STR_PAUSEMSG "请按任意键继续. . ."
extern void sh_pause(void);
#ifndef INIT_PAUSE # ifdef _DEBUG
# define INIT_PAUSE() atexit(sh_pause)
# else
# define INIT_PAUSE() (void) /* 别说了,都重新开始吧 */
# endif #endif/* !INIT_PAUSE */ //12.0 判断是大端序还是小端序,大端序返回true
extern bool sh_isbig(void); /**
* sh_free - 简单的释放内存函数,对free再封装了一下
**可以避免野指针
**pobj:指向待释放内存的指针(void*)
**/
extern void sh_free(void** pobj); /**
* 获取 当前时间串,并塞入tstr中长度并返回
** 使用举例
char tstr[64];
sh_times(tstr, LEN(tstr));
puts(tstr);
**tstr : 保存最后生成的最后串
**len : tstr数组的长度
** : 返回tstr首地址
**/
extern int sh_times(char tstr[], int len); #endif/* ! _H_SCHEAD */

schead.c

#include <schead.h>

//简单通用的等待函数
void
sh_pause(void)
{
rewind(stdin);
printf(_STR_PAUSEMSG);
getchar();
} //12.0 判断是大端序还是小端序,大端序返回true
bool
sh_isbig(void)
{
static union {
unsigned short _s;
unsigned char _c;
} __u = { };
return __u._c == ;
} /**
* sh_free - 简单的释放内存函数,对free再封装了一下
**可以避免野指针
**@pobj:指向待释放内存的指针(void*)
**/
void
sh_free(void** pobj)
{
if (pobj == NULL || *pobj == NULL)
return;
free(*pobj);
*pobj = NULL;
} #if defined(_MSC_VER)
/**
* Linux sys/time.h 中获取时间函数在Windows上一种移植实现
**tv : 返回结果包含秒数和微秒数
**tz : 包含的时区,在window上这个变量没有用不返回
** : 默认返回0
**/
int
gettimeofday(struct timeval* tv, void* tz)
{
time_t clock;
struct tm tm;
SYSTEMTIME wtm; GetLocalTime(&wtm);
tm.tm_year = wtm.wYear - ;
tm.tm_mon = wtm.wMonth - ; //window的计数更好写
tm.tm_mday = wtm.wDay;
tm.tm_hour = wtm.wHour;
tm.tm_min = wtm.wMinute;
tm.tm_sec = wtm.wSecond;
tm.tm_isdst = -; //不考虑夏令时
clock = mktime(&tm);
tv->tv_sec = (long)clock; //32位使用,接口已经老了
tv->tv_usec = wtm.wMilliseconds * ; return _RT_OK;
}
#endif /**
* 获取 当前时间串,并塞入tstr中C长度并返回
** 使用举例
char tstr[64];
puts(gettimes(tstr, LEN(tstr)));
**tstr : 保存最后生成的最后串
**len : tstr数组的长度
** : 返回tstr首地址
**/
int
sh_times(char tstr[], int len)
{
struct tm st;
time_t t = time(NULL);
localtime_r(&t, &st);
return (int)strftime(tstr, len, "%F %X", &st);
}

scatom.h

#ifndef _SC_ATOM
#define _SC_ATOM /*
* 作者 : wz
*
* 描述 : 简单的原子操作,目前只考虑 VS(CL) 小端机 和 gcc
* 推荐用 posix 线程库
*/ // 如果 是 VS 编译器
#if defined(_MSC_VER) #include <Windows.h> //忽略 warning C4047: “==”:“void *”与“LONG”的间接级别不同
#pragma warning(disable:4047) // v 和 a 多 long 这样数据
#define ATOM_FETCH_ADD(v, a) \
InterlockedExchangeAdd((LONG*)&(v), (LONG)(a)) #define ATOM_ADD_FETCH(v, a) \
InterlockedAdd((LONG*)&(v), (LONG)(a)) #define ATOM_SET(v, a) \
InterlockedExchange((LONG*)&(v), (LONG)(a)) #define ATOM_CMP(v, c, a) \
(c == InterlockedCompareExchange((LONG*)&(v), (LONG)(a), (LONG)c)) /*
对于 InterlockedCompareExchange(v, c, a) 等价于下面
long tmp = v ; v == a ? v = c : ; return tmp; 咱么的 ATOM_FETCH_CMP(v, c, a) 等价于下面
long tmp = v ; v == c ? v = a : ; return tmp;
*/
#define ATOM_FETCH_CMP(v, c, a) \
InterlockedCompareExchange((LONG*)&(v), (LONG)(a), (LONG)c) #define ATOM_LOCK(v) \
while(ATOM_SET(v, )) \
Sleep() #define ATOM_UNLOCK(v) \
ATOM_SET(v, ) //否则 如果是 gcc 编译器
#elif defined(__GNUC__) #include <unistd.h> /*
type tmp = v ; v += a ; return tmp ;
type 可以是 8,16,32,84 的 int/uint
*/
#define ATOM_FETCH_ADD(v, a) \
__sync_fetch_add_add(&(v), (a)) /*
v += a ; return v;
*/
#define ATOM_ADD_FETCH(v, a) \
__sync_add_and_fetch(&(v), (a)) /*
type tmp = v ; v = a; return tmp;
*/
#define ATOM_SET(v, a) \
__sync_lock_test_and_set(&(v), (a)) /*
bool b = v == c; b ? v=a : ; return b;
*/
#define ATOM_CMP(v, c, a) \
__sync_bool_compare_and_swap(&(v), (c), (a)) /*
type tmp = v ; v == c ? v = a : ; return v;
*/
#define ATOM_FETCH_CMP(v, c, a) \
__sync_val_compare_and_swap(&(v), (c), (a)) /*
加锁等待,知道 ATOM_SET 返回合适的值
_INT_USLEEP 是操作系统等待纳秒数,可以优化,看具体操作系统 使用方式
int lock = 0;
ATOM_LOCK(lock); //to do think ... ATOM_UNLOCK(lock); */
#define _INT_USLEEP (2)
#define ATOM_LOCK(v) \
while(ATOM_SET(v, )) \
usleep(_INT_USLEEP) /*
对ATOM_LOCK 解锁, 当然 直接调用相当于 v = 0;
*/
#define ATOM_UNLOCK(v) \
__sync_lock_release(&(v)) #endif /*!_MSC_VER && !__GNUC__ */ #endif /*!_SC_ATOM*/

Makefile

C = gcc
DEBUG = -g -Wall -D_DEBUG
#指定pthread线程库
LIB = -lpthread -lm
#指定一些目录
DIR = -I./module/schead/include -I./module/struct/include -I./module/service/include
#具体运行函数
RUN = $(CC) $(DEBUG) -o $@ $^ $(LIB) $(DIR)
RUNO = $(CC) $(DEBUG) -c -o $@ $^ $(DIR) # 主要生成的产品
all:test_cjson_write.out test_csjon.out test_csv.out test_json_read.out test_log.out\
test_scconf.out test_tstring.out test_sctimer.out #挨个生产的产品
test_cjson_write.out:test_cjson_write.o schead.o sclog.o tstring.o cjson.o
$(RUN)
test_csjon.out:test_csjon.o schead.o sclog.o tstring.o cjson.o
$(RUN)
test_csv.out:test_csv.o schead.o sclog.o sccsv.o tstring.o
$(RUN)
test_json_read.out:test_json_read.o schead.o sclog.o sccsv.o tstring.o cjson.o
$(RUN)
test_log.out:test_log.o schead.o sclog.o
$(RUN)
test_scconf.out:test_scconf.o schead.o scconf.o tree.o tstring.o sclog.o
$(RUN)
test_tstring.out:test_tstring.o tstring.o sclog.o schead.o
$(RUN)
test_sctimer.out:test_sctimer.o schead.o sctimer.o
$(RUN) #产品主要的待链接文件
test_cjson_write.o:./main/test_cjson_write.c
$(RUNO)
test_csjon.o:./main/test_csjon.c
$(RUNO)
test_csv.o:./main/test_csv.c
$(RUNO)
test_json_read.o:./main/test_json_read.c
$(RUNO)
test_log.o:./main/test_log.c
$(RUNO) -std=gnu99
test_scconf.o:./main/test_scconf.c
$(RUNO)
test_tstring.o:./main/test_tstring.c
$(RUNO)
test_sctimer.o:./main/test_sctimer.c
$(RUNO) #工具集机械码,待别人链接
schead.o:./module/schead/schead.c
$(RUNO)
sclog.o:./module/schead/sclog.c
$(RUNO)
sccsv.o:./module/schead/sccsv.c
$(RUNO)
tstring.o:./module/struct/tstring.c
$(RUNO)
cjson.o:./module/schead/cjson.c
$(RUNO)
scconf.o:./module/schead/scconf.c
$(RUNO)
tree.o:./module/struct/tree.c
$(RUNO)
sctimer.o:./module/service/sctimer.c
$(RUNO) #删除命令
clean:
rm -rf *.i *.s *.o *.out __* log ; ls -hl
.PHONY:clean

你需要找到从Makefile中找到 关于 test_sctimer.h 的编译代码.

目前关于 simple c linux上代码结构如下

等再搞个大补丁再统一上传到githup上吧. 目前还是以分享为主, 自己测试测试. 再小的模块, 都不好做.因为你想做好.

后记

  错误是难免,欢迎交流指正.结构决定算法,算法优化结构. 环境限制代码...codeing...

静静的看着你装逼  http://music.163.com/#/song?id=402070795

C 实现一个跨平台的定时器 论述的更多相关文章

  1. 开源一个跨平台运行的服务插件 - TaskCore.MainForm

    本次将要很大家分享的是一个跨平台运行的服务插件 - TaskCore.MainForm,此框架是使用.netcore来写的,现在netcore已经支持很多系统平台运行了,所以将以前的Task.Main ...

  2. 拥抱.NET Core,如何开发一个跨平台类库 (1)

    在此前的文章中详细介绍了使用.NET Core的基本知识,如果还没有看,可以先去了解“拥抱.NET Core,学习.NET Core的基础知识补遗”,以便接下来的阅读. 在本文将介绍如何配置类库项目支 ...

  3. acl 是一个跨平台的网络通信库及服务器编程框架

    acl 工程是一个跨平台(支持LINUX,WIN32,Solaris,MacOS,FreeBSD)的网络通信库及服务器编程框架,同时提供更多的实用功能库.通过该库,用户可以非常容易地编写支持多种模式( ...

  4. MvvmCross[翻译] 使用Xamarin与MvvmCross完成一个跨平台App

    总览 原文:https://github.com/MvvmCross/MvvmCross/wiki/Tip-Calc-A-first-app 我们所做的第一个Model-View-ViewModel( ...

  5. 如何做一个跨平台的游戏App?

    如何做一个跨平台的游戏App? iOS和安卓系统上的应用程序,根据提供的内容不同,按照开发方式和用户体验不同,可区分为app和游戏: 首先从开发方式不同来说明,app开发一般是用操作系统官方提供的开发 ...

  6. 使用electron+vue开发一个跨平台todolist(便签)桌面应用

    # 1 最近一直在使用electron开发桌面应用,对于一个web开发者来说,html+javascript+css的开发体验让我非常舒服.之前我一直简单的以为electron只是张网页加个壳,和那些 ...

  7. 如何用 Electron + WebRTC 开发一个跨平台的视频会议应用

    在搭建在线教育.医疗.视频会议等场景时,很多中小型公司常常面临 PC 客户端和 Web 端二选一的抉择.Electron 技术的出现解决了这一难题,只需前端开发就能完成一个跨平台的 PC 端应用.本文 ...

  8. Dapps-是一个跨平台的应用服务商店

    简介 Dapps 是一个跨平台的应用商店,包含众多软件,基于docker dapps是什么? 它是一个应用程序商店,包含丰富的软件,因为基于docker,使你本机电脑有云开发的效果. 一键安装程序:多 ...

  9. [Java定时器]用Spring Task实现一个简单的定时器.

    今天做一个项目的的时候需要用到定时器功能.具体需求是: 每个月一号触发一次某个类中的方法去拉取别人的接口获取上一个月份车险过期的用户.如若转载请附上原文链接:http://www.cnblogs.co ...

随机推荐

  1. android View 自动 GONE 问题

    首先说一下 view visibility VISIBLE.INVISIBLE.GONE的区别: 可见(visible) XML文件:android:visibility="visible& ...

  2. JS入门-慕课网

    javascript是一种弱类型的数据交互语言, ch 1 数据类型 js中有六种数据类型:nunmber.string.boolean.null.undenfined.object原始类型:numb ...

  3. Android开发-API指南-<meta-data>

    <meta-data> 英文原文:http://developer.android.com/guide/topics/manifest/meta-data-element.html 采集( ...

  4. MSP430F149学习之路——时钟1

    1.看门狗产生方波 #include <msp430x14x.h> void main() { WDTCTL = WDT_MDLY_32; IE1 |= WDTIE; P1DIR |= B ...

  5. 学习练习 java 线程

    package com.hanqi.xc; import java.util.*; public class lianxi extends Thread { public void run() { c ...

  6. 华为OJ平台——计算字符串的相似度

    题目描述: 对于不同的字符串,我们希望能有办法判断相似程度,我们定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法如下: 1 修改一个字符,如把“a”替换为“b”. 2 增加一个字符,如 ...

  7. Linux:两台服务器之间添加信任关系,进行远程操作的时候不需要输入密码

    两台机器之间建立信任关系的步骤: 1. 在机器1上root用户执行ssh-keygen命令,生成建立安全信任关系的证书,直接Enter [root@CentOS64-x64 ~]# ssh-keyge ...

  8. Magento修改css样式更新之——grunt命令使用

    1.清除pub/static和var中相应文件 2.源头文件重新导入pub/static 3.pub中的less编译 4.字面翻译是跟踪源头文件变化实时编译,但是这里的the source files ...

  9. Android IOS WebRTC 音视频开发总结(五六)-- 如何测试网络性能?

    本文主要介绍如何测试网络性能,文章来自博客园RTC.Blacker,欢迎关注微信公众号blacker,更多详见www.rtc.help 网络性能直接决定了视频通话效果,比如qq,很多时候我们我们觉得通 ...

  10. vs2012 快捷键修改

    打开:工具-->选项 搜索:剪切行 移除原有的 Crtl+L 命令 改为:Ctrl+D