一,线程的概念
1,线程是操作系统能够进行运算调度的最小的单位。
2,线程被包含在进程中,是进程中实际运作的单位。就是说,线程在进程的地址空间里运行。
3,一条线程指进程中一个单一顺序的控制流。
4,一个进程中可以并发多个线程,每条线程的执行任务不同。
5,从内核的角度理解线程。
在内核中看到的线程也是PCB来表示的,创建新的PCB和原PCB共用虚拟地址空间。
Linux中使用进程来模拟实现线程,这种线程也被称之为轻量级进程。
也就是说在Linux看来,没有什么线程和进程的区别,这样也就统一了线程和进程的管理。
二,线程与线程的联系
1,线程没有高低之分,每个线程都是等级都是一样的。
2,同一进程内部的线程间共享部分资源。
1,同一虚拟地址空间,(本质上式多个线程的PCB公用同一个页表)
因此代码,全局数据,堆都是共享的,加入定义一个函数,那么在各个线程中都可以调用,
定义一个全局变量,在个线程中都能访问。
就是说:两个进程间肯定不能访问同一个函数,
因为根本就不再同一个地址空间中,但是两个线程可以,因为他们公用了同一地址空间。
2,文件描述符表。
3,线程间也有私有的空间
1,独立的调用栈。要是共用同一调用栈,多个进程切换使用就会把调用栈写坏了。
2,上下文信息。
二,线程与进程的对比
什么叫进程:用一组PCB来表示,用途是资源的分配和管理
什么叫线程; 用一个PCB来表示,用途是资源的调度和执行
举个例子:
- 打开迅雷软件—–向系统内核索要资源,启动“迅雷”进程,。
- 开始下载一个电影—–从索要的资源中调度分配一部分资源,启动下载线程。
- 开始播放电影—–再索要的资源中调度分配一部分资源次从,启动播放线程。
线程的优点:
1,线程占用的资源要比进程少,创建一个新线程的代价更小,意思就是和主线程公用一份地址空间。
2,线程间的切换也更简单,需要操作系统做的工作更少。
3,线程之间共享数据更容易。
线程的缺点:
1,编码/调试的难度提高。
2,缺乏访问控制:一个线程崩溃,会导致整个进程都异常终止。(因为他们公用虚拟地址空间)
二,线程的控制
系统调用:就是操作系统内核所提供的一些功能,然后以一些c语言风格的函数接口,暴露给用户。(如 fork,wait,exec)。
操作系统并没有提供线程的概念,用户态才有线程的概念。
所以并没有什么直接操作线程的系统调用,线程的相关操作都是以库函数的形式提供的(posix线程库)。
1,创建线程函数
int pthread_create (pthread_t * thread ,const pthread_attr_t *attr, void*(*start_routine)(void*),void* arg);
参数:
thread:输出型参数 ,将返回这个线程的id;
attr:一般填空,表示默认
start_routine:函数指针,这个函数就是这个线程将要执行函数。
arg:要执行函数的参数。有多个的话就传结构体。
返回值:
成功返回0,失败返回错误码
2,编写一个简单的线程代码
//p表示POSIX 线程库
#include<pthread.h>
#include<unistd.h>
#include<stdio.h>
void* Enter(void* arg)
{
(void) arg;
while(1)
{
printf("I am thread");
sleep(1);
}
}
int main ()
{
pthread_t tid;
pthread_create(&tid,NULL,Enter,NULL);
while(1)
{
printf("I am main!\n");
sleep(1);
}
return 0;
}
现再来看看是否有是两个线程了,具体就是查看是否有两个PCB
然后来查看进程组的PCB
图中的Thread和LWP都是线程ID,但是两者属于不同的范畴
Thread:是属于线程库的范畴,线程库的后序操作都是根据这个线程ID来完成的。使用thread_self(),可以获得。
从图中它是一个地址,其实就是地址空间中这个线程的地址。
LWP: 是属于进程调度的范畴。但是线程是轻量级进程,使用这个ID,无法调度,所以有了Thread。
那么一个进程就是由若干个线程构成的,起码有一个,就是主线程,这若干个线程叫线程组。
线程组的id就是进程的id,就是主线程的id。
3,线程终止函数
1, 线程自己return返回
2,线程在内部调用pthread_exit函数结束自己。
void pthread_exit(void * value_ptr)
//value_ptr不能是栈变量
3,一个线程使用pthread_cancel函数结束掉同一线程组的某个线程。
int pthread_cancel (pthread_t tid)
//tid 就是 线程ID
强烈不推荐使用第三种方法。
4,线程的等待和分离
为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复⽤刚才退出线程的地址空间
就是说,等到线程结束主线程需要将已退出的线程的资源进行清理。
int pthread_join (pthread_t thread , void ** value_ptr)
一般都将参数类型设为void*,因为 一个无类型的指针,大小是固定的,将两边都设为void*,就能实现传参, value_ptr是 void* * 再加一个* 是为了传指针,做输出参数,函数返回就可直接用value_ptr了
thread :线程ID
value_ptr:输出的参数
返回值:成功返回0
void**value_ptr:这是一个输出参数,线程以不同的方式结束,这个输出的参数的值也是不一样的。
如果是return方式返回,value_ptr就是return的值的指针。
如果是pthreadexit 方式返回,value_ptr 就是传给pthread_ptr函数的参数(value_ptr)的指针。
如果是pthread_cancel的方式返回,value_ptr就是一个常数的指针。
如果不关心就填空。
线程分离:
1,另一个线程分离另一个线程
int pthread_detach(pthread_t tid)
2,线程自己分离自己
int pthread_detach(pthread_self())