一、什么是线程?
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
1、【线程技术发展】
Linux 2.2内核
•不存在真正意义上的线程
Linux 2 .4内核
•消除线程个数的限制,允许动态地调整进程数上限
在Linux 内核2.6之前,进程是最主要的处理调度单元,并没支持内核线程机制
进程所维护的是程序所包含的资源(静态资源), 如: 地址空间, 打开的文件句柄集, 文件系统状态, 信号处理handler, 等;线程所维护的运行相关的资源(动态资源), 如: 运行栈, 调度相关的控制信息, 待处理的信号集, 等;
然而, 一直以来, linux内核并没有线程的概念. 每一个执行实体都是一个task_struct结构, 通常称之为进程. 进程是一个执行单元, 维护着执行相关的动态资源. 同时, 它又引用着程序所需的静态资源(注意这里说的是linux中的进程).通过系统调用clone创建子进程时, 可以有选择性地让子进程共享父进程所引用的资源. 这样的子进程通常称为轻量级进程。
linux上的线程就是基于轻量级进程, 由用户态的pthread库实现的.使用pthread以后, 在用户看来, 每一个task_struct就对应一个线程, 而一组线程以及它们所共同引用的一组资源就是一个进程.
在linux 2.6以前, pthread线程库对应的实现是一个名叫linuxthreads的lib. linuxthreads利用前面提到的轻量级进程来实现线程。
Linux 2.6内核
•实现共享地址空间的进程机制, 在1996年第一次获得线程的支持
为了改善LinuxThread问题,根据新内核机制重新编写线程库, 改善Linux对线程的支持
•由IBM主导的新一代POSIX线程库(Next Generation POSIX Threads,简称为NGPT)
–NGPT项目在2002年启动
–为了避免出现有多个Linux线程标准,在2003年停止该项目
•由Red Hat主导的本地化POSIX线程库 (Native POSIX Thread Library,简称为NTPL)
–最早在Red Hat Linux9中被支持
–现在已经成为GNU C 函数库的一部分,同时也成为Linux线程的标准
所以,现在的Linux系统下的多线程遵循POSIX线程接口,称为pthread,在Linux编写多线程程序需要包含头文件pthread.h。
2、【什么时候使用多线程】#include <pthread.h>
当然,进包含一个头文件是不能搞定线程的,还需要连接libpthread.so这个库,因此在程序连接阶段应该有类似这样的指令:
gcc program.o -o program -lpthread
当多个任务可以并行执行时,可以为每个任务启动一个线程。
二、线程的管理
在2.6内核中引入了线程组的概念,在2.6内核中,如果使用ps命令看,一个多线程的进程,只会显示一个进程,在给线程组中的任何一个线程发送信号的时候,整个线程组中的进程都能收到信号。
struct task_struct { ... pid_t pid; pid_t tgid; ... struct task_struct *group_leader; /* threadgroup leader */ ... struct list_head thread_group; ... };
在linux 2.6中,内核有了线程组的概念,task_struct结构中增加了一个tgid(thread group id)字段。
pid,从字面上是process id,但其实是thread id。(线程id)
tgid,从字面上,应该是thread group id,也就是真正的process id。(进程id)
如果这个task是一个"主线程",则它的tgid等于pid,否则tgid等于进程的pid(即主线程的pid)。在clone系统调用中,传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid)。
有了tgid,内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程。
而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid,而tast_struct中的pid则由gettid系统调用来返回。
例如:在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。
为了应付"发送给进程的信号"和"发送给线程的信号", task_struct里面维护了两套signal_pending, 一套是线程组共享的, 一套是线程独有的.通过kill发送的信号被放在线程组共享的signal_pending中, 可以由任意一个线程来处理; 通过pthread_kill发送的信号(pthread_kill是pthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending中, 只能由本线程来处理.当线程停止/继续, 或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中。#include<unistd.h> #include<stdlib.h> #include<stdio.h> #include<unistd.h> #include<sys/syscall.h> #include<pthread.h> pid_t gettid(void) { return syscall(SYS_gettid); } void* doit(void*); int main(void) { pthread_t tid; pthread_create(&tid,NULL,doit,NULL); printf("main pid = %d \n",getpid()); printf("main tid = %d \n",gettid()); pause();//主线程挂起否则主线程终止,子线程也就挂了) } void* doit(void* agr) { printf("thread is created!\n"); printf("thread pid = %d \n",getpid()); printf("thread tid = %d \n",gettid()); pause(); //挂起线程 }
运行结果:main pid = 11973main tid = 11973thread is created!thread pid = 11973thread tid = 11974终端中ps -a后结果:PID TTY TIME CMD11973 pts/6 00:00:00 pthread12067 pts/7 00:00:00 ps终端中执行:kill 11974 也可以杀死pthread进程
三、线程函数
1、创建
int pthread_create (pthread_t *__restrict __newthread,//新创建的线程ID __const pthread_attr_t *__restrict __attr,//线程属性 void *(*__start_routine) (void *),//新创建的线程从start_routine开始执行 void *__restrict __arg)//执行函数的参数
参数:pthread_create()接口的第一个参数是一个返回参数。当一个新的线程调用成功之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程 进行管理。
pthread_create()接口的第二个参数用于设置线程的属性。这个参数是可选的,当不需要修改线程的默认属性时,给它传递NULL就行。具体线程有那些属性,我们后面再做介绍。
当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是start_routine()。start_routine()函数有一个参数,这个参数就是pthread_create的最后一个参数arg。这种设计可以在线程创建之前就帮它准备好一些专有数据,最典型的用法就是使用C++编程时的this指针。start_routine()有一个返回值,这个返回值可以通过pthread_join()接口获得。
返回值:成功-0,失败-返回错误编号,可以用strerror(errno)函数得到错误信息。
int pthread_join(pthread_t thread, void **value_ptr);
参数:thread:等待退出线程的线程号。value_ptr:退出线程的返回值。含义:使一个线程等待另一个线程结束。
作用:代码中如果没有使用pthread_join函数,主线程会很快结束导致整个进程结束,使创建的线程没有开始执行就结束了。加入pthread_join函数 后,主线程会一直等待直到pthread指定的线程结束才结束,使创建的线程有机会执行。
NOTE:另外需要说明的是,一个线程不能被多个线程等待,也就是说对一个线程只能调用一次pthread_join,否则只有一个能正确返回,其他的将返 回ESRCH 错误。
#include <stdio.h> #include <pthread.h> void* thread( void *arg ) { printf( "This is a thread and arg = %d.\n", *(int*)arg); *(int*)arg = 0; return arg; } int main( int argc, char *argv[] ) { pthread_t th; int ret; int arg = 10; int *thread_ret = NULL; ret = pthread_create( &th, NULL, thread, &arg ); if( ret != 0 ){ printf( "Create thread error!\n"); return -1; } printf( "This is the main process.\n" ); pthread_join( th, (void**)&thread_ret ); printf( "thread_ret = %d.\n", *thread_ret ); return 0; }
void pthread_exit(void *retval)
参数:retval:pthread_exit()调用线程的返回值功能:使用函数pthread_exit 退出线程 ,这是线程的主动行为