通过线程设计一个延迟运行的函数,但不挂起当前线程。
掌握要点:
1.怎么创建一个脱离线程。
2.怎么用select()实现一个延迟函数。
说明:
timeout(int,void *(*)(void*),void *);在指定秒数为负数或任何线程执行函数失败时都直接运行指定的延迟执行函数。
1.[代码][C/C++]代码
#include "unp.h"
/*以下是为使用超时函数定义的接口数据和函数*/
typedef void *(*pthread_f)(void *);
typedef struct toinfo_t{
pthread_f to_func;
void*to_args;
timeval_ttv;
}toinfo_t;
void*timeout_ext(toinfo_t *args){
int n;
__again:
n=select(1,0,0,0,&args->tv);
if(n==-1){
if(errno!=EINTR)
print_err("select()");
fprintf(stderr,"select() interrupt by sigint\n");
goto __again;
}
args->to_func(args->to_args);
free(args);
return (void*)0;
}
int create_detach_thread(pthread_f func,void *args){
pthread_attr_t attr;
pthread_t tid;
intflag;
charbuf[BUF_ERR_SIZE];
flag=pthread_attr_init(&attr);
if(flag!=0){
fprintf(stderr,"%s error:%s\n","pthread_attr_init()",geterr_str(flag,buf,sizeof(buf)));
goto __err;
}
flag=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
if(flag!=0){
fprintf(stderr,"%s error:%s\n","pthread_attr_setdetachstate()",geterr_str(flag,buf,sizeof(buf)));
goto __err0;
}
flag=pthread_create(&tid,&attr,(pthread_f)func,args);
if(flag!=0){
fprintf(stderr,"%s error:%s\n","pthread_create()",geterr_str(flag,buf,sizeof(buf)));
}
__err0:
flag=pthread_attr_destroy(&attr);
if(flag!=0){
fprintf(stderr,"%s error:%s\n","pthread_attr_destroy()",geterr_str(flag,buf,sizeof(buf)));
flag==0;
}
__err:
return flag;
}
void timeout(int secs,pthread_f func,void *args){
int flag;
pthread_attr_t attr;
toinfo_t *toinfo;
pthread_t tid;
if(secs>0){
toinfo=(toinfo_t*)malloc(sizeof(toinfo_t));
if(toinfo!=NULL){
toinfo->tv.tv_sec=secs;
toinfo->to_args=args;
toinfo->to_func=func;
if(create_detach_thread((pthread_f)timeout_ext,toinfo)==0)
return;
free(toinfo);
}
}
func(args);
return;
}
/*以下用户自定义的函数和数据*/
void *user_timeout_func(timeval_t *args){
timeval_ttv,stv;
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
stv.tv_sec=tv.tv_sec-args->tv_sec;
if(tv.tv_usec>args->tv_usec){
stv.tv_usec=tv.tv_usec-args->tv_usec;
}
else{
stv.tv_sec-=1;
stv.tv_usec=1*1000000-args->tv_usec+tv.tv_usec;
}
fprintf(stderr,"escape time sec %lu ,msec %lu\n",stv.tv_sec,stv.tv_usec);
}
voidintr_handler(int signo){
return;
}
void *delay(void *args){
int i,j;
for(i=0;i<10000;i++){
for(j=0;i<1000;j++)
;
}
return (void*)0;
}
int main(int argc,char **argv){
timeval_t tv;
sigset_t sm;
int i;
if(argc!=2){
fprintf(stderr,"usage %s \n",argv[0]);
exit(EXIT_FAILURE);
}
handle_signal(SIGINT,intr_handler);
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
timeout(atoi(argv[1]),(pthread_f)user_timeout_func,&tv);
sigemptyset(&sm);
sigaddset(&sm,SIGINT);
sigsuspend(&sm);
return 1;
}
2.[代码]程序的优化
编译运行以上程序:
gcc -o thread_timeout_func01 thread_timeout_func01.c unp.c -g -w -lpthread
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func01 5
^Cselect() interrupt by sigint
^Cselect() interrupt by sigint
escape time sec 5 ,msec 4435
^\Quit
root@U-SERVER:/home/apu/pthread_test#
分析:
1.主线程信号屏蔽为只接收SIGQUIT(Ctrl+\)。
2.当我们按下(Ctrl+C)时其他线程会收到了此信号,select()被中断,且select()不会被系统自动重启,而需要我们从用户应用层去重启,重启后select()的timeval_t参数变为剩余时间。所用超时时间的控制是比较精确的(我们也可以从应用层控制,但效果不如select()从内核控制来得好)。
3.但超时结束时,可以看出时间流逝是5秒4435微秒,几乎接近5秒这个单位。
以上测试主程序,是在2个线程的情况下测试的。现在我们来模拟一个繁忙的系统,即CPU的时间片会快速在进程、线程间切换:
voidintr_handler(int signo){
fprintf(stderr,"tid %lu caught sigint\n",(unsigned long)pthread_self());
return;
}
void *delay(void *args){
int i,j;
for(i=0;i<10000;i++){
for(j=0;i<1000;j++)
;
}
fprintf(stderr,"one thread end\n");
return (void*)0;
}
int main(int argc,char **argv){
timeval_t tv;
sigset_t sm;
int i;
if(argc!=3){
fprintf(stderr,"usage %s \n",argv[0]);
exit(EXIT_FAILURE);
}
handle_signal(SIGINT,intr_handler);
for(i=0;i
create_detach_thread(delay,0);
}
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
fprintf(stderr,"start runing timeout()\n");
timeout(atoi(argv[1]),(pthread_f)user_timeout_func,&tv);
sigemptyset(&sm);
sigaddset(&sm,SIGINT);
sigsuspend(&sm);
return 1;
}
创建多个分离线程,每个线程都循环执行一个空语句,已保证CPU在切换。
编译并运行。
root@U-SERVER:/home/apu/pthread_test# gcc -o thread_timeout_func01 thread_timeout_func01.c unp.c -g -w -lpthread
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func01 10 10
start runing timeout()
^Ctid 3075857264 caught sigint
^Ctid 3075857264 caught sigint
^Ctid 3075857264 caught sigint
escape time sec 10 ,msec 17164
^\Quit
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func01 10 100
start runing timeout()
^Ctid 3076782960 caught sigint
^Ctid 3076782960 caught sigint
^Ctid 3076782960 caught sigint
^Ctid 3076782960 caught sigint
^Cescape time sec 10 ,msec 141697
tid 3076782960 caught sigint
^\Quit
root@U-SERVER:/home/apu/pthread_test#
说明:
1.信号只会被其中一个线程接收,且是固定的(如果第一个接收信号的线程没有结束)。
2.从select()没有被中断可以看出,优先运行的线程优先接收信号。
3.系统越繁忙,计时越不精准。
修改接口函数timeout(),timeout_ext(),添加时间流逝控制,增加精确度。
void timeout(int secs,pthread_f func,void *args){
timeval_t tv;
int flag;
pthread_attr_t attr;
toinfo_t *toinfo;
pthread_t tid;
if(secs>0){
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
tv.tv_sec+=secs;
toinfo=(toinfo_t*)malloc(sizeof(toinfo_t));
if(toinfo!=NULL){
bcopy(&tv,&toinfo->tv,sizeof(timeval_t));
toinfo->to_args=args;
toinfo->to_func=func;
if(create_detach_thread((pthread_f)timeout_ext,toinfo)==0)
return;
free(toinfo);
}
}
func(args);
return;
}
void*timeout_ext(toinfo_t *args){
timeval_ttv,stv;
int n;
if(gettimeofday(&tv,0)==-1)
print_err("gettimeofday()");
if(args->tv.tv_sec>tv.tv_sec ||(args->tv.tv_sec==tv.tv_sec && args->tv.tv_usec>tv.tv_usec)){
stv.tv_sec=args->tv.tv_sec-tv.tv_sec;
if(args->tv.tv_usec>tv.tv_usec){
stv.tv_usec=args->tv.tv_usec-tv.tv_usec;
}
else{
stv.tv_sec-=1;
stv.tv_usec=1*1000000+args->tv.tv_usec-tv.tv_usec;
}
__again:
n=select(1,0,0,0,&stv);
if(n==-1){
if(errno!=EINTR)
print_err("select()");
fprintf(stderr,"select() interrupt by sigint\n");
goto __again;
}
}
args->to_func(args->to_args);
free(args);
return (void*)0;
}
编译运行:
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func01 10 100
start runing timeout()
^Ctid 3076782960 caught sigint
^Ctid 3076782960 caught sigint
^Ctid 3076782960 caught sigint
^Ctid 3076782960 caught sigint
^Cescape time sec 10 ,msec 141697
tid 3076782960 caught sigint
^\Quit
root@U-SERVER:/home/apu/pthread_test# gcc -o thread_timeout_func thread_timeout_func.c unp.c -g -w -lpthread
root@U-SERVER:/home/apu/pthread_test# ./thread_timeout_func 10 100
start runing timeout()
^Ctid 3076725616 caught sigint
escape time sec 10 ,msec 8565
^\Quit
root@U-SERVER:/home/apu/pthread_test#
从结果来看精准度得到了很大的提高,如果是秒级别的延迟。这点精度丢失不算什么,但如果是毫秒,甚至微妙级别的,这样的精度丢失就尤为严重了。100个线程(进程)运行,误差不到10毫秒,还是非常不错的。