1.虚拟地址空间
管理进程的pcb结构体task_struct中有一个mm_struct指针,指向虚拟地址空间,而编译好的代码中有地址,但是是虚拟地址,那么虚拟地址是怎么转化成真实的物理地址呢?通过页表转化
我们知道,代码中的地址都是虚拟地址,每行代码都有对应的地址,比如一个函数,可能是连续的代码地址,构成代码块,也就是说,一个函数对应一批连续的虚拟地址,那么,我们把10个函数拆分一下,可不可以呢?在技术上是可以实现的,我们就有了线程的出现,
物理内存
OS进行内存管理,不是以字节为单位的,而是以内存块为单位的,默认大小是4KB,操作系统和磁盘文件进行IO的基本单位是4KB,对于磁盘来说就是8个扇区,而在内存中,每4KB大小内存,叫做页块或页帧,内存是由一个个页框组成的,
2.线程的概念
线程在进程内部运行,是CPU调度的基本单位
我们以前理解的进程 进程=内核数据结构+代码和数据 ,在内核观点看来,进程是承担分配操作系统资源的实体
而线程就是task_strust,(大概是这样的,不太准确),
linux中没有管理线程的结构体,而是用task_struct模拟线程,在linux中,他们都是执行流,叫做轻量级进程
2.1 什么是线程
- 线程是一个进程内部的控制序列
- 每个进程都至少有一个线程
- 线程在进程内部运行,本质上是在进程地址空间运行
- 通过进程虚拟地址空间可以看到进程的大部分资源,将进程资源合理的分配给每个执行流,就形成了线程执行流
2.2 线程的优点
- 创建一个新线程要比创建一个进程代价小的多,为什么?
因为进程有进程虚拟地址空间,而代码和数据在内存中,在执行一个进程时,cpu会把经常用到的代码(比如循环中的代码)加载到cpu中的cache快速储存器中,这样执行代码时效率会比较快,而如果我们创建一个进程,那么cpu中的cache也要重新加载代码,这样代价就比较大了,而创建一个线程,只需要创建一个task_struct,虚拟地址空间还是原来的,只需要映射的时候改动一下,所以说线程的创建代价比较小
- 线程占用的资源要比进程小很多
- 能充分利用多处理器的可并行数量
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作
2.3 线程的缺点
线程不具有健壮性,也就是说,一个线程出问题了,那么整个进程都要被杀掉,或者在一个多线程进程中,因为时间分配上的偏差,导致因为共享了不该共享的变量而造成不良影响,也就是说,线程是缺乏保护的,
2.4线程共享的数据
线程ID 一组寄存器 栈 errno 信号屏蔽字 调度优先级
每个线程都有自己的栈空间,用于存储局部变量、函数参数和返回地址等
每个线程在执行时都有自己的寄存器状态,说明线程可以动态运行,也就是多线程可以并发执行
2.5 使用线程
#include<iostream>
#include<unistd.h>
#include<ctime>
void* threadStart(void* args)
{
while(true)
{
sleep(1);
std::cout << "new thread running..." << ", pid: " << getpid()<< std::endl;
}
}
int main()
{
srand(time(nullptr));
//创建一个线程
pthread_t tid;
pthread_create(&tid,nullptr,threadStart,(void*)"thread-new");
//主线程
while(true)
{
std::cout<<"main thread running... "<<", pid: "<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
3.线程的控制
对于linux来说,没有线程,只有轻量级进程(就是线程),在linux中对轻量级进程进行分装,系统调用只会给上层用户提供创建轻量级进程的接口,而其他接口操作系统是不提供的,但是他提供了线程库,通过线程库,我们可以创建线程,终止线程,调用线程,等待线程
所以我们在编译使用线程的代码时,要链接线程库
g++ -o testthread testthread.cc -std=c++11 -lpthread
关于线程控制的几个问题
- 主线程和新线程谁先运行? 不确定,顺序可能发生变化
- 主线程应该最后退出
- 线程函数传参,我们可以传任何类型,类对象也可以
- 线程函数返回,只考虑正确的返回,因为线程出现异常,那么整个进程就退出了
- 新线程可以使用return结束,或者pthread_exit结束线程
- 一个线程被创建,默认是必须被join的,但是如果一个线程处于分离状态,就可以不用join了
关于线程的一些函数
pthread_create
int pthread_create(pthread_t* tidp, const pthread_attr_t* attr,
void* (*start_rtn)(void*), void* arg);
pthread_create函数用于创建一个新线程
void* (*start_rtn)(void*)
:线程函数的指针,即新线程将从这个函数开始执行。void* restrict arg
:传递给线程函数的参数。如果没有参数,则设为NULL
。- 成功时返回0,失败时返回错误码
pthread_join
int pthread_join(pthread_t thread, void **retval);
pthread_join用于等待指定的线程结束
pthread_t thread
:要等待的线程的ID。void **retval
:一个指向void*
类型的指针的指针,用于存储线程的返回值。如果不需要线程的返回值,可以传递NULL
- 成功时返回0,失败时返回错误码。
- pthread_join函数会阻塞调用他的线程,直到指定的线程结束
pthread_exit
void pthread_exit(void *retval);
pthread_exit用于结束当前线程
void *retval
:一个指向线程返回值的指针。这个值会被主线程或其他线程通过pthread_join
函数获取。pthread_exit
函数不会返回。一旦调用,当前线程将终止。- 传递给
pthread_exit
的值可以被等待该线程的主线程或其他线程通过pthread_join
函数获取。
pthread_cancel
int pthread_cancel(pthread_t thread);
pthread_cancel函数用于请求取消指定的线程
pthread_t thread
:要取消的线程的ID。- 成功时返回0,失败时返回错误码。
pthread_detach
int pthread_detach(pthread_t thread);
pthread_detach
将一个线程设置为分离状态(detached state)。分离线程在结束执行时不会留下任何需要清理的资源,因此不需要其他线程调用 pthread_join
来等待它结束和回收资源。
pthread_t thread
:要设置为分离状态的线程的ID。- 成功时返回0,失败时返回错误码。
- 一个线程只能被设置为分离状态一次,一旦线程被分离,它不能再被
pthread_join
。
pthread_self
pthread_t pthread_self(void);
- 不需要任何参数,并且返回调用它的线程的线程ID。
- 返回当前线程的
pthread_t
类型的线程ID