二、Linux进程VS线程
1、进程和线程
概念:
进程是资源分配的基本单位
线程是调度的基本单位
线程共享进程数据,但也有线程自己独有的数据:
线程ID
一组寄存器中线程自己的上下文数据
栈
errno
信号屏蔽字(handler方法是共享的)
调度优先级
线程中共享的数据:
代码段和数据段
文件描述符表
每种信号的处理方式
当前工作目录
用户id和组id
注:进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到
进程和线程的关系图:
三、Linux线程控制
1、POSIX线程库
pthread线程库是应用层的原生线程库:
应用层指的是这个线程库并不是系统接口直接提供的,而是由第三方提供的
原生指的是大部分Linux系统都会默认带上该线程库
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些函数库,要通过引入头文件<pthreaad.h>
链接这些线程函数库时,要使用编译器命令的“-lpthread”选项
错误检查:
传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误
pthreads函数出错时不会设置全局变量errno(而大部分POSIX函数会这样做),而是将错误代码通过返回值返回
pthreads同样也提供了线程内的errno变量,以支持其他使用errno的代码。对于pthreads函数的错误,建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小
2、线程创建
pthread_create函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
解释:
功能:创建一个新的线程
参数:thread:输出型参数,返回获取线程ID;attr:设置线程的属性,attr为NULL表示使用默认属性;start_routine:是个函数地址,线程启动后要执行的函数,该函数返回值为void *,参数为void *;arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
注意:
主线程调用pthread_create函数创建一个新线程,此后新线程就会跑去执行参入的函数,而主线程则继续往下执行
对于执行函数来说,参数和返回值的类型都是void *,void *是一个通用的类型,可以传入或者返回数据和其他类型的指针,从而传入和带出多样的类型和数据
示例:
mypthread.c: #include<stdio.h> #include<unistd.h> #include<pthread.h> #include<stdlib.h> #include<string.h> int val=0; void* Routine(void* avgs) { while(1) { printf("I am %s... val:%d\n",(char*)avgs,val); sleep(1); } } int main() { pthread_t tid1,tid2,tid3; int ret1=pthread_create(&tid1,NULL,Routine,(void*)"pthread 1"); if(ret1!=0) { fprintf(stderr,"pthread_creat:%s\n",strerror(ret1)); exit(1); } int ret2=pthread_create(&tid2,NULL,Routine,(void*)"pthread 2"); if(ret2!=0) { fprintf(stderr,"pthread_creat:%s\n",strerror(ret2)); exit(1); } int ret3=pthread_create(&tid3,NULL,Routine,(void*)"pthread 3"); if(ret3!=0) { fprintf(stderr,"pthread_creat:%s\n",strerror(ret3)); exit(1); } while(1) { printf("I am main pthread...val:%d\n",val++); sleep(1); } return 0; } Makefile: mypthread:mypthread.c gcc -o $@ $^ -pthread .PHONY:clean clean: rm -f mypthread
注意:
默认情况下,ps命令不带-L,看到的就是一个个的进程;带-L就可以查看到每个进程内的多个轻量级进程
在Linux中,应用层的线程与内核的LWP是一一对应的,实际上操作系统调度的时候采用的是LWP,而并非PID,只不过我们之前接触到的都是单线程进程,其PID和LWP是相等的
3、线程ID及线程地址空间布局
概念:
pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事
前面讲的线程ID(LWP)属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的
在Linux系统层面有LWP与线程对应,但是Linux是用轻量级进程模拟的线程,而对于用户来说,并不会关心底层实现,从用户角度来说,他们也需要知道线程的信息,状态以及操作线程,由此在共享区中还相应的构建了TCB(线程控制块),便于用户操作线程,在用户区进行维护
pthread_ self函数原型:
pthread_t pthread_self(void);
功能:获得线程自身的ID
注:对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址
- 示图:
注:主线程并不使用动态库里的线程栈,而是使用进程里的栈