Linux教程1LINUX多线程1.Linux多线程概述1.1.概述进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。这就造成进程在进行切换等操作时都需要有比较负责的上下文切换等动作。为了进一步减少处理器的空转时间,支持多处理器和减少上下文切换开销,也就出现了线程。线程通常叫做轻量级进程。线程是在共享内存空间中并发执行的多道执行路径,是一个更加接近于执行体的概念,拥有独立的执行序列,是进程的基本调度单元,每个进程至少都有一个main线程。它与同进程中的其他线程共享进程空间{堆代码数据文件描述符信号等},只拥有自己的栈空间,大大减少了上下文切换的开销。线程和进程在使用上各有优缺点:线程执行开销小,占用的CPU资源少,线程之间的切换快,但不利于资源的管理和保护;而进程正相反。从可移植性来讲,多进程的可移植性要好些。同进程一样,线程也将相关的变量值放在线程控制表内。一个进程可以有多个线程,也就是有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间。要注意的是,由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他线程带来影响。1.2.线程分类按调度者分为用户级线程和核心级线程·用户级线程:主要解决上下文切换问题,调度算法和调度过程全部由用户决定,在运行时不需要特定的内核支持。缺点是无法发挥多处理器的优势·核心级线程:允许不同进程中的线程按照同一相对优先调度方法调度,发挥多处理器的并发优势现在大多数系统都采用用户级线程和核心级线程并存的方法。一个用户级线程可以对应一个或多个核心级线程,也就是“一对一”或“一对多”模型。Linux教程21.3.线程创建的Linux实现Linux的线程是通过用户级的函数库实现的,一般采用pthread线程库实现线程的访问和控制。它用第3方posix标准的pthread,具有良好的可移植性。编译的时候要在后面加上–lpthread创建退出等待多进程fork()exit()wait()多线程pthread_createpthread_exit()pthread_join()//并清理线程Pthread_detach();自动清理线程并可以执行其它线程pthread_cancel();线程的取消int*pi=(int*)malloc(4);//s申请内存feel(pi);释放内存pthread_cleanup_push(fun,(void*)1);把fun入栈pthread_cleanup_pop(0);把fun出栈,0为整型参数2.线程的创建和退出创建线程实际上就是确定调用该线程函数的入口点,线程的创建采用函数pthread_create。在线程创建以后,就开始运行相关的线程函数,在该函数运行完之后,线程就退出,这也是线程退出的一种方式。另一种线程退出的方式是使用函数pthread_exit()函数,这是线程主动退出行为。这里要注意的是,在使用线程函数时,不能随意使用exit退出函数进行出错处理,由于exit的作用是使调用进程终止,往往一个进程包括了多个线程,所以在线程中通常使用pthread_exit函数来代替进程中的退出函数exit。由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以通过wait()函数系统调用来同步终止并释放资源一样,线程之间也有类似的机制,那就是pthread_join函数。pthread_join函数可以用于将当前线程挂起,等待某个线程的结束。这个函数是一个线程阻塞函数,调用这函数的线程将一直等待直到被等待的线程结束为止,当函数返回时,被等待线程的资源被回收。函数原型:#includepthread.hintpthread_create(pthread_t*thread,pthread_attr_t*attr,void*(*start_routine)(void*),void*arg);voidpthread_exit(void*retval);通常的形式为:pthread_tpthid;pthread_create(&pthid,NULL,pthfunc,NULL);或pthread_create(&pthid,NULL,pthfunc,(void*)3);pthread_exit(NULL);或pthread_exit((void*)3);//3作为返回值被后面的pthread_join函数捕获。函数pthread_create用来创建线程。返回值:成功,则返回0;失败,则返回-1。各参数描述如下:·参数thread是传出参数,保存新线程的标识;·参数attr是一个结构体指针,结构中的元素分别指定新线程的运行属性,attr可以用pthread_attr_init等函数设置各成员的值,但通常传入为NULL即可;Linux教程3·参数start_routine是一个函数指针,指向新线程的入口点函数,线程入口点函数带有一个void*的参数由pthread_create的第4个参数传入;·参数arg用于传递给第3个参数指向的入口点函数的参数,可以为NULL,表示不传递。函数pthread_exit表示线程的退出。其参数可以被其它线程用pthread_join函数捕获。示例:#includestdio.h#includepthread.h#includestdlib.h#includesys/stat.hvoid*fun(void*p)//函数指针fun,参数的p值为123{inti;for(i=1;i10;i++){printf(world,p=%d\n,p);sleep(1);}pthread_exit(200);//退出,清理线程,返回值为200}intmain(){pthread_tid;inta,i;pthread_create(&id,NULL,fun,123);//创建线程,&id传入线程的id号;一般为NULL;fun函数指针,123为传入函数指针的实参for(i=1;i10;i++){printf(hello\n);sleep(1);}pthread_join(id,&a);//等待线程退出,接收线程返回值200存入内存地址单元a里return0;//pthread_detach(id);让指定id的线程退出后自动清理资源}编译时需要带上线程库选项:gcc-oaa.c-lpthread另外,编译的时候会产生一些警告信息,是因为某些参数的类型不匹配,这个不影响生成可执行程序,可以不管它,直接运行即可,下同。3.线程的等待退出3.1.等待线程退出线程从入口点函数自然返回,或者主动调用pthread_exit()函数,都可以让线程正常终止线程从入口点函数自然返回或者调用函数退出时,函数返回值可以被其它线程用pthread_join函数获取Linux教程4pthread_join原型为:#includepthread.hintpthread_join(pthread_tpthid,void**thread_return);1.该函数是一个阻塞函数,一直等到参数pthid指定的线程返回;与多进程的wait或waitpid类似。thread_return是一个传出参数,接收线程函数的返回值。如果线程通过调用pthread_exit()终止,则pthread_exit()中的参数相当于自然返回值,照样可以被其它线程用pthread_join获取到。Example:返回值的例子#includestdio.h#includepthread.h#includestdlib.h#includesys/stat.hvoid*fun(void*p)//参数的p值为123{inti;for(i=1;i10;i++){if(i==5)pthread_exit(200);//return200;printf(world,p=%d\n,p);sleep(1);}}intmain(){pthread_tid;inta,i;pthread_create(&id,NULL,fun,123);for(i=1;i10;i++){printf(hello\n);sleep(1);}pthread_join(id,&a);printf(a=%d\n,a);return0;}运行可以发现,不论是pthread_exit还是return都会导致子进程退出,并且返回值200都会保存到变量a中。即pthread_join函数的第二个参数传的是哪个变量的地址,那么就会把子线程退出的返回值保存到哪个变量里。而不要被它的第二个参数的形式void**thread_return以及pthread(void*retval)的形式所迷惑。这个例子编译时会产生一些警告信息,是因为某些参数类型不匹配,如果要去掉警告信息,可以在相关参数前面加上强制类型转换即可。如下面的示例。但无论是加不加强制类型转换,此时都不影响其值的传递和程序运行结果。故也不要被程序中的强制类型转换所迷惑,它不会对运行结果有任何影响,学习的时候读者可以直接无视他们。前面的例子故意不加强制转换产生警告,是为了突出这些函数的根本特征。示例:(加上强制类型转换后的示例程序)Linux教程5#includestdio.h#includepthread.h#includestdlib.h#includesys/stat.hvoid*fun(void*p)//参数的p值为123{inti;for(i=1;i10;i++){if(i==5)pthread_exit((void*)200);//return(void*)200;printf(world,p=%d\n,(int)p);sleep(1);}}intmain(){pthread_tid;inta,i;pthread_create(&id,NULL,fun,(void*)123);for(i=1;i10;i++){printf(hello\n);sleep(1);}pthread_join(id,(void**)&a);printf(a=%d\n,a);return0;}2.该函数还有一个非常重要的作用,由于一个进程中的多个线程共享数据段,因此通常在一个线程退出后,退出线程所占用的资源并不会随线程结束而释放。如果该线程类型并不是自动清理资源类型的,则该线程退出后,线程本身的资源必须通过其它线程调用pthread_join来清除,这相当于多进程程序中的waitpid。3.2.线程的取消线程也可以被其它线程杀掉,在Linux中的说法是一个线程被另一个线程取消(cancel)。线程取消的方法是一个线程向目标线程发cancel信号,但是如何处理cancel信号则由目标线程自己决定,目标线程或者忽略、或者立即终止、或者继续运行至cancelation-point(取消点)后终止。根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于Linux线程库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCELLinux教程6信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:pthread_testcancel();retcode=read(fd,buffe