Linux中线程使用详解
线程与进程
为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
一、线程标识
- 线程有ID, 但不是系统唯一, 而是进程环境中唯一有效.
- 线程的句柄是pthread_t类型, 该类型不能作为整数处理, 而是一个结构.
下面介绍两个函数:
- 头文件: <pthread.h>
- 原型: int pthread_equal(pthread_t tid1, pthread_t tid2);
- 返回值: 相等返回非0, 不相等返回0.
- 说明: 比较两个线程ID是否相等.
- 头文件: <pthread.h>
- 原型: pthread_t pthread_self();
- 返回值: 返回调用线程的线程ID.
二、线程创建
在执行中创建一个线程, 可以为该线程分配它需要做的工作(线程执行函数), 该线程共享进程的资源. 创建线程的函数pthread_create()
- 头文件: <pthread.h>
- 原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg);
- 返回值: 成功则返回0, 否则返回错误编号.
- 参数:
- tidp: 指向新创建线程ID的变量, 作为函数的输出.
- attr: 用于定制各种不同的线程属性, NULL为默认属性(见下).
- start_rtn: 函数指针, 为线程开始执行的函数名.该函数可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由 pthread_join()获取
- arg: 函数的唯一无类型(void)指针参数, 如要传多个参数, 可以用结构封装.
linux下多线程程序的编译方法:
因为pthread的库不是linux系统的库,所以在进行编译的时候要加上 -lpthread
# gcc filename -lpthread //默认情况下gcc使用c库,要使用额外的库要这样选择使用的库
例1:thread_create.c
1 #include <stdio.h> 2 #include <pthread.h> //包线程要包含 3 void *mythread1(void) 4 { 5 int i; 6 for(i=0;i<100;i++) 7 { 8 printf("this is the 1st pthread,created by zieckey.\n"); 9 sleep(1); 10 } 11 } 12 void *mythread2(void) 13 { 14 int i; 15 for(i=0;i<100;i++) 16 { 17 printf("this is the 2st pthread,created by zieckey.\n"); 18 sleep(1); 19 } 20 } 21 int main() 22 { 23 int ret=0; 24 pthread_tid1,id2; 25 ret=pthread_create(&id1,NULL,(void*)mythread1,NULL); 26 if(ret) 27 { 28 printf("create pthread error!\n"); 29 return -1; 30 } 31 ret=pthread_create(&id2,NULL,(void*)mythread2,NULL); 32 if(ret) 33 { 34 printf("create pthread error!\n"); 35 return -1; 36 } 37 pthread_join(id1,NULL); 38 pthread_join(id2,NULL); 39 40 return 0; 41 } 42 编译步骤:gcc thread_create .c -lpthread -othread_create
例2: thread_int.c //向线程函数传递整形参数
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 void *create(void *arg) 5 { 6 int *num; 7 num=(int *)arg; 8 printf("create parameter is %d \n",*num); 9 return (void *)0; 10 } 11 int main(int argc,char *argv[]) 12 { 13 pthread_t tidp; 14 int error; 15 int test=4; 16 int*attr=&test; 17 18 error=pthread_create(&tidp,NULL,create,(void*)attr); 19 if(error) 20 { 21 printf("pthread_create is created is not created...\n"); 22 return -1; 23 } 24 sleep(1); 25 printf("pthread_create is created...\n"); 26 return 0; 27 } 28 注:字符串,结构参数,一样道理
三 线程的合并与分离
4. 线程的属性
- int pthread_attr_init(pthread_attr_t *attr);
- int pthread_attr_destory(pthread_attr_t *attr);
4.1 绑定属性
- #include <stdio.h>
- #include <pthread.h>
- ……
- int main( int argc, char *argv[] )
- {
- pthread_attr_t attr;
- pthread_t th;
- ……
- pthread_attr_init( &attr );
- pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
- pthread_create( &th, &attr, thread, NULL );
- ……
- }
4.2 分离属性
- pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);
- #include <stdio.h>
- #include <pthread.h>
- ……
- int main( int argc, char *argv[] )
- {
- pthread_attr_t attr;
- pthread_t th;
- ……
- pthread_attr_init( &attr );
- pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
- pthread_create( &th, &attr, thread, NULL );
- ……
- }
4.3 调度属性
- struct sched_param {
- int sched_priority;
- }
- int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);
- int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <pthread.h>
- #define THREAD_COUNT 12
- void show_thread_policy( int threadno )
- {
- int policy;
- struct sched_param param;
- pthread_getschedparam( pthread_self(), &policy, ¶m );
- switch( policy ){
- case SCHED_OTHER:
- printf( "SCHED_OTHER %d\n", threadno );
- break;
- case SCHED_RR:
- printf( "SCHDE_RR %d\n", threadno );
- break;
- case SCHED_FIFO:
- printf( "SCHED_FIFO %d\n", threadno );
- break;
- default:
- printf( "UNKNOWN\n");
- }
- }
- void* thread( void *arg )
- {
- int i, j;
- long threadno = (long)arg;
- printf( "thread %d start\n", threadno );
- sleep(1);
- show_thread_policy( threadno );
- for( i = 0; i < 10; ++i ) {
- for( j = 0; j < 100000000; ++j ){}
- printf( "thread %d\n", threadno );
- }
- printf( "thread %d exit\n", threadno );
- return NULL;
- }
- int main( int argc, char *argv[] )
- {
- long i;
- pthread_attr_t attr[THREAD_COUNT];
- pthread_t pth[THREAD_COUNT];
- struct sched_param param;
- for( i = 0; i < THREAD_COUNT; ++i )
- pthread_attr_init( &attr[i] );
- for( i = 0; i < THREAD_COUNT / 2; ++i ) {
- param.sched_priority = 10;
- pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );
- pthread_attr_setschedparam( &attr[i], ¶m );
- pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );
- }
- for( i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i ) {
- param.sched_priority = 20;
- pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );
- pthread_attr_setschedparam( &attr[i], ¶m );
- pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );
- }
- for( i = 0; i < THREAD_COUNT; ++i )
- pthread_create( &pth[i], &attr[i], thread, (void*)i );
- for( i = 0; i < THREAD_COUNT; ++i )
- pthread_join( pth[i], NULL );
- for( i = 0; i < THREAD_COUNT; ++i )
- pthread_attr_destroy( &attr[i] );
- return 0;
- }
4.4 堆栈大小属性
- int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
4.5 满栈警戒区属性
- int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
- 原型: void pthread_exit(void *rval_ptr);
- 头文件: <pthread.h>
- 参数: rval_ptr是一个无类型指针, 指向线程的返回值存储变量.
pthread_join函数:
- 原型: int pthread_join(pthread_t thread, void **rval_ptr);
- 头文件: <pthread.h>
- 返回值: 成功则返回0, 否则返回错误编号.
- 参数:
- thread: 线程ID.
- rval_ptr: 指向返回值的指针(返回值也是个指针).
- 说明:
- 调用线程将一直阻塞, 直到指定的线程调用pthread_exit, 从启动例程返回或被取消.
- 如果线程从它的启动例程返回, rval_ptr包含返回码.
- 如果线程被取消, 由rval_ptr指定的内存单元置为: PTHREAD_CANCELED.
- 如果对返回值不关心, 可把rval_ptr设为NULL.
实例:
1 #include <pthread.h> 2 #include <stdio.h> 3 4 /* print process and thread IDs */ 5 void printids(const char *s) 6 { 7 pid_t pid, ppid; 8 pthread_t tid; 9 pid= getpid(); 10 ppid = getppid(); 11 tid = pthread_self(); 12 printf("%16s pid %5u ppid %5u tid %16u (0x%x) ", 13 s, (unsigned int)pid, (unsigned int)ppid, 14 (unsigned int)tid, (unsigned int)tid); 15 } 16 /* thread process */ 17 void *thread_func(void *arg); 18 { 19 printids("new thread: "); 20 return (void *)108; 21 } 22 /* main func */ 23 int main() 24 { 25 int err; 26 void *tret; /* thread return value */ 27 pthread_t ntid; 28 err = pthread_create(&ntid, NULL, thread_func, NULL); 29 if (err != 0) 30 perror("can't create thread"); 31 32 err = pthread_join(ntid, &tret); 33 if (err != 0) 34 perror("can't join thread"); 35 printids("main thread: "); 36 printf("thread exit code: %d ", (int)tret); 37 sleep(1); 38 return 0; 39 }
pthread_cancel函数:
pthread_cancel函数发送终止信号
pthread_setcancelstate函数设置终止方式
pthread_testcancel函数取消线程(另一功能是:设置取消点)
1) 线程取消的定义
一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。
2) 线程取消的语义
线程取消的方法是向目标线程发Cancel信号(pthread_cancel函数发送Cancel信号),但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态(pthread_setcancelstate函数设置状态)决定。
线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。
3 )取消点
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用 pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
4 )程序设计方面的考虑
如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。
5 )与线程取消相关的pthread函数
int pthread_cancel(pthread_t thread)
发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
int pthread_setcancelstate(int state, int *oldstate)
设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为 NULL则存入原来的Cancel状态以便恢复。
int pthread_setcanceltype(int type, int *oldtype)
设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和 PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。
void pthread_testcancel(void)
功能一:设置取消点;
功能二:检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。
代码:
1 #include <stdio.h> 2 #include <errno.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <pthread.h> 6 7 8 #define THREAD_MAX 4 9 10 11 pthread_mutex_t mutex; 12 pthread_t thread[THREAD_MAX]; 13 14 15 static int tries; 16 static int started; 17 18 19 void print_it(int *arg) 20 { 21 pthread_t tid; 22 tid = pthread_self(); 23 printf("Thread %lx was canceled on its %d try.\n",tid,*arg); 24 } 25 26 27 void *Search_Num(int arg) 28 { 29 pthread_t tid; 30 int num; 31 int k=0,h=0,j; 32 int ntries; 33 tid = pthread_self(); 34 35 /*while(pthread_mutex_trylock(&mutex) == EBUSY) 36 { 37 printf("**************busy****************\n"); 38 pthread_testcancel(); 39 }*/ 40 srand(arg); 41 num = rand()&0xFFFFFF; 42 //pthread_mutex_unlock(&mutex); 43 44 printf("thread num %lx\n",tid); 45 46 ntries = 0; 47 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); 48 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL); 49 50 pthread_cleanup_push((void *)print_it,(void *)&ntries); 51 52 while(1) 53 { 54 num = (num+1)&0xffffff; 55 ntries++; 56 57 if(arg == num) 58 { 59 //只允许一个线程操作此处 60 while(pthread_mutex_trylock(&mutex) == EBUSY) { 61 //一个线程操作后其余线程进入次循环挂起,等待pthread_cancel函数发送cancel信号终止线程 62 k++; 63 if(k == 10000) 64 { 65 printf("----------2busy2-----------\n"); 66 } 67 68 pthread_testcancel(); 69 } 70 tries = ntries; 71 //pthread_mutex_unlock(&mutex); //如果加上这句话,将会有好几个线程找到主函数中设定的值pid 72 printf("Thread %lx found the number!\n",tid); 73 74 for(j = 0;j<THREAD_MAX;j++) 75 { 76 if(thread[j]!=tid) 77 { 78 pthread_cancel(thread[j]); 79 } 80 } 81 82 break; 83 } 84 if(ntries%100 == 0) 85 { 86 h++; 87 /*线程阻塞,其他线程争夺资源,或者是等待pthread_cancel函数发送cancel信号终止线程*/ 88 pthread_testcancel(); 89 /*这是为了弄明白pthread_testcancel函数的作用而设置的代码段*/ 90 if(h == 10000) 91 { 92 h = 0; 93 printf("----------thread num %lx-------------\n",tid); 94 } 95 } 96 } 97 pthread_cleanup_pop(0); 98 return (void *)0; 99 } 100 101 102 int main() 103 { 104 int i,pid; 105 106 pid = getpid(); //设置要查找的数 107 108 pthread_mutex_init(&mutex,NULL); 109 printf("Search the num of %d\n",pid); 110 for(started = 0; started < THREAD_MAX; started++) 111 { 112 pthread_create(&thread[started],NULL,(void *)Search_Num,(void *)pid); 113 } 114 115 for(i = 0; i < THREAD_MAX; i++) 116 { 117 printf("-----------i = %d--------------\n",i); 118 pthread_join(thread[i],NULL); 119 } 120 printf("It took %d tries ot find the number!\n",tries); 121 return 0; 122 } 123 运行结果: 124 Search the num of 6531 125 -----------i = 0-------------- 126 thread num b6fbcb70 127 thread num b67bbb70 128 thread num b5fbab70 129 thread num b77bdb70 130 ----------thread num b67bbb70------------- 131 Thread b67bbb70 found the number! 132 ----------thread num b6fbcb70------------- 133 ----------thread num b77bdb70------------- 134 ----------2busy2----------- 135 ----------thread num b5fbab70------------- 136 ----------2busy2----------- 137 Thread b5fbab70 was canceled on its 1174527 try. 138 Thread b77bdb70 was canceled on its 1023100 try. 139 -----------i = 1-------------- 140 Thread b6fbcb70 was canceled on its 1174527 try. 141 -----------i = 2-------------- 142 -----------i = 3-------------- 143 It took 1174527 tries ot find the number!
1 <span style="font-size: small;">#include <stdlib.h> 2 #include <stdio.h> 3 #include <pthread.h> 4 5 void cleanup(void *arg) 6 { 7 printf("cleanup: %s\n", (char *)arg); 8 } 9 10 void *thr_fn1(void *arg) 11 { 12 printf("thread 1 start\n"); 13 pthread_cleanup_push(cleanup, "thread 1 first handler"); 14 pthread_cleanup_push(cleanup, "thread 1 second handler"); 15 printf("thread 1 push complete\n"); 16 if (arg) 17 return((void *)1); 18 // pthread_exit((void *)2); 19 20 pthread_cleanup_pop(0); 21 pthread_cleanup_pop(0); 22 // return((void *)1); 23 pthread_exit((void *)2); 24 25 } 26 27 void *thr_fn2(void *arg) 28 { 29 printf("thread 2 start\n"); 30 pthread_cleanup_push(cleanup, "thread 2 first handler"); 31 pthread_cleanup_push(cleanup, "thread 2 second handler"); 32 printf("thread 2 push complete\n"); 33 if (arg) 34 pthread_exit((void *)2); 35 pthread_cleanup_pop(0); 36 pthread_cleanup_pop(0); 37 pthread_exit((void *)2); 38 } 39 40 int main(void) 41 { 42 int err; 43 pthread_t tid1, tid2; 44 void *tret; 45 46 err = pthread_create(&tid1, NULL, thr_fn1, (void *)1); 47 if (err != 0) 48 printf("can't create thread 1: %c\n", strerror(err)); 49 err = pthread_create(&tid2, NULL, thr_fn2, (void *)1); 50 if (err != 0) 51 printf("can't create thread 2: %c\n", strerror(err)); 52 err = pthread_join(tid1, &tret); 53 if (err != 0) 54 printf("can't join with thread 1: %c\n", strerror(err)); 55 printf("thread 1 exit code %d\n", (int)tret); 56 err = pthread_join(tid2, &tret); 57 if (err != 0) 58 printf("can't join with thread 2: %c\n", strerror(err)); 59 printf("thread 2 exit code %d\n", (int)tret); 60 exit(0); 61 } 62 </span>
pthread_detach()函数:
创建一个线程默认的状态是joinable。
如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码).
所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代 码,回收其资源(类似于wait,waitpid) 。
但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。
比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以
1)在子线程中加入代码pthread_detach(pthread_self())
2)父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)
这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
pthread_kill()函数:
pthread_kill与kill有区别,是向线程发送signal。,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数。
int pthread_kill(pthread_t thread, int sig);
向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。
pthread_kill(threadid, SIGKILL)杀死整个进程。 如果要获得正确的行为,就需要在线程内实现signal(SIGKILL,sig_handler)。所以,如果int sig的参数不是0,那一定要清楚到底要干什么,而且一定要实现线程的信号处理函数,否则,就会影响整个进程。
如果int sig是0呢,这是一个保留信号,一个作用是用来判断线程是不是还活着。pthread_kill的返回值: 成功:0 线程不存在:ESRCH 信号不合法:EINVAL
代码:
int kill_rc = pthread_kill(thread_id,0); if(kill_rc == ESRCH) printf("the specified thread did not exists or already quit\n"); else if(kill_rc == EINVAL) printf("signal is invalid\n"); else printf("the specified thread is alive\n");
这里附上线程基本函数:
线程属性pthread_attr_t简介
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <errno.h> 4 #include <pthread.h> 5 static void pthread_func_1 (void); 6 static void pthread_func_2 (void); 7 8 int main (int argc, char** argv) 9 { 10 pthread_t pt_1 = 0; 11 pthread_t pt_2 = 0; 12 pthread_attr_t atrr = {0}; 13 int ret = 0; 14 15 /*初始化属性线程属性*/ 16 pthread_attr_init (&attr); 17 pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); 18 pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); 19 20 ret = pthread_create (&pt_1, &attr, pthread_func_1, NULL); 21 if (ret != 0) 22 { 23 perror ("pthread_1_create"); 24 } 25 26 ret = pthread_create (&pt_2, NULL, pthread_func_2, NULL); 27 if (ret != 0) 28 { 29 perror ("pthread_2_create"); 30 } 31 32 pthread_join (pt_2, NULL); 33 34 return 0; 35 } 36 37 static void pthread_func_1 (void) 38 { 39 int i = 0; 40 41 for (; i < 6; i++) 42 { 43 printf ("This is pthread_1.\n"); 44 45 if (i == 2) 46 { 47 pthread_exit (0); 48 } 49 } 50 51 return; 52 } 53 54 static void pthread_func_2 (void) 55 { 56 int i = 0; 57 58 for (; i < 3; i ++) 59 { 60 printf ("This is pthread_2.\n"); 61 } 62 63 return; 64 }