线程概念
线程是一个执行流,线程在进程内部执行。是操作系统调度的最小单位!一切线程都在进程内部执行,对比进程,线程与进程之间的区别和联系具体如何,下面从各个角度进行分析。
页表
回顾进程,先前,我们统一认为进程=进程的代码+数据+进程内核数据结构,以Linux系统为例,这里的内核数据结构就是task_struct和mm_struct,这里需要着重研究mm_struct,以下是mm_struct的每个分区的数据存储的情况:
对于页表,页表项,页框,页帧这几个概念我们必须进行区分:
页表:负责进行虚拟地址空间和物理内存空间的地址转换
页表项:记录每一个虚实地址转换的信息
页框:物理内存的最小划分单位
那么一个进程在内存中是按照地址空间进行划分的。在进程没有调入内存的时候,它就是一个程序。 更准确地描述,程序就是磁盘上的一个文件。那么既然进程是按照地址空间进行划分的。那么在编译器对写好的代码文件编译的时候,这个程序早就已经按照对应的进程地址空间的划分方式进行了划分。 准确地来说,一个程序文件也被分成了好几块。而每一个文件块,对应的都是4kB. 假如说,地址空间是4GB,如果对应一个地址就是一个页表项。那么光存放一个页表就要占掉不小的内存。同样,由于一一对应的页表项必须是连续存储。即使按照4Kb一个空间进行划分,也仍旧需要占用不小的连续的存储空间。所以为了解决这个问题,引入了多级页表的结构。这里以二级页表为例:
而这些工作都离不开硬件单元MMU的支持,当我们非法访存操作的时候。系统就是通过检测对应的地址转换机构发现了非法请求,通知操作系统发送信号终止进程。
线程 vs进程 &Linux如何看待进程和线程
线程和进程对比,主要有如下的优势
- 线程切换的开销小,每次切换。操作系统都要维护对应的上下文信息。说白了,就是大量的寄存器里面的临时数据!而线程涉及到切换的时候,由于线程是共享进程的大部分资源的。因此在涉及进程切换的时候,无需切换地址空间和页表。开销比较小
- 线程间通信方便。由于进程互相独立性较强,导致两个进程间交互数据的成本相对高。但是同一个进程内部的线程是可以看到对方绝大多数的资源。
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- IO密集型应用,为了提高性能,将IO操作重叠,线程可以同时等待不同的IO操作
说明:计算密集任务:是指那些以计算为主的任务。比如数据加密解密,大数据查找。
IO密集型应用:以IO操作为主的任务,比如读写磁盘、访问数据库等等操作。
对于Linux系统而言,在操作系统认为。进程和线程,拥有绝大多数相似的行为,对于这样高度相似的两个实体。Linux并没有重新为线程独立设计一个新的数据结构,也就是在Linux操作系统认为,无论是线程还是进程,它看到的只有一个结构--->task_struct,所以线程对于Linux系统而言,有一个新的名字--->轻量级进程
#Linux查看线程的命令
ps -aL
Linux下线程的使用
这里要说明一件事,就是Linux创建线程并不是Linux系统原生提供的系统调用,而是调用的第三方库--->pthread,每一个使用Linux内核的系统都会提供这个pthread库
创建线程
创建线程使用的是pthread_create这个函数接口,对应的函数说明如下:
#include <pthread.h>
int pthread_create( pthread_t *thread,\
const pthread_attr_t *attr,\
void *(*start_routine) (void *) , \
void *arg);
第一个参数是线程的id,第二个参数是线程的属性(设置成nullptr就行),第三个是我们希望线程执行的任务(回调函数),第四个参数是对应传递给回调函数的参数。下面是一个简单的demo代码
//在编译的时候带上选项-lpthread
//pthread_create函数调用
#include <iostream>
#include<pthread.h>
#include<unistd.h>
void* routine(void* args)
{
(void)args; //消除告警
while(true)
{
std::cout<<"我是创建的线程,我在不停执行任务"<<std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,routine,nullptr);
while(true)
{
std::cout<<"我是主线程,我一直跑"<<std::endl;
sleep(1);
}
return 0;
}
调用线程创建接口以后,当前的进程就会分出至少两个线程:一个是主线程,另一个是子线程,其中
- 同一个进程内,任意一个线程的退出都会导致所有线程异常退出。
- 主线程通常需要执行子线程退出以后的善后工作。
pthread_join
主线程通常需要进行子线程退出的善后工作,这个善后工作就是pthread_join函数,,先来看对应函数的说明:
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
第一个参数对应的是线程的参数,第二个参数对应参数的含义比较复杂,今天作为简单使用就不进行拓展,默认设置为nullptr即可,下面是demo代码:
#include <iostream>
#include<pthread.h>
#include<unistd.h>
void* routine(void* args)
{
(void)args; //消除告警
while(true)
{
std::cout<<"我是创建的线程,我在不停执行任务"<<std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,routine,nullptr);
while(true)
{
std::cout<<"我是主线程,我一直跑"<<std::endl;
sleep(1);
}
//等待线程退出
pthread_join(tid,nullptr);
return 0;
}
线程终止
如果需要终止线程,可以使用如下两个接口
#include <pthread.h>
int pthread_cancel(pthread_t thread);
void pthread_exit(void *retval);
线程分离
有的情况下,我们不需要主线程进行对于某些子线程的善后处理。这个时候就可以调用线程分离函数来对线程进行分离。对应的接口是pthread_detch,来看对应的函数声明
#include <pthread.h>
int pthread_detach(pthread_t thread);
传入对应的线程id就可以了