文章目录
- 线程的概念
- 进程和线程对比
- 线程的控制
- 创建线程与分配任务
- 线程终止
- 线程等待
- 线程分离
- pthread线程库
线程的概念
线程是我们经常听到的一个概念,他和进程有什么关系呢
从操作系统课本里我们可能听说过,线程是一个微缩版的进程,他拥有TCB,不会被分配资源,是CPU进行调度的单位
我们其实可以更直观的理解,程序中的一个执行路线我们就可以称之为线程,也可以说是执行流
那么一个进程他的执行路线至少有一个,也就算做是一个线程,这个进程本身也可以创建分支线程,那么原来的这个进程也就叫做主线程
在Linux中,线程其实没有自己的TCB,而是和进程公用PCB,内存空间也是共享的
因此在Linux中线程其实是要比进程更加轻量化的
每个线程都拥有自己的PCB,但都是自己从主线程中写时拷贝来的
在CPU的视角来看,他的所调度的所有单位都是PCB,是同一个进程,CPU也无法分辨他运行的是线程还是进程
进程和线程对比
线程其实是被CPU调度运行的的基本单位,因为一个CPU只能运行一个执行流
而进程是操作系统分配资源的基本单位,也就是说这个进程和他所属的线程内存空间全部共享,一个全局变量,不同线程之间也能共享
线程也拥有自己的特征数据,否则就无法对线程进行管理了
线程ID,栈区资源,信号屏蔽字,调度优先级
共享的资源和环境有
文件描述符表,信号处理方式,工作目录,用户ID,组ID
我们可以通过ps -aL
查看线程ID
LWP就是对应进程ID的线程ID
线程的控制
创建线程与分配任务
第三方POSIX线程库在编译时需要引头文件pthread.h
,编译选项需要链接库-lpthread
一共有四个参数,第一个是线程ID,第二个是选项,目前只设置为空指针即可,第三个选项是需要让新线程走的执行流,第四个是传递给其中的参数
需要注意的是,这个执行流的函数指针是固定的
使用示例
定义参数类
using Tfunc = std::function<void()>;
class TData
{
public:
TData(const std::string str, const uint64_t time, Tfunc f)
:TName(str), CreatTime(time), func(f)
{}
public:
std::string TName; // 线程名称
uint64_t CreatTime; // 时间戳
Tfunc func; // 执行的回调函数
};
void Print()
{
std::cout<<"线程执行的任务函数"<<std::endl;
}
定义执行流
void* TRountine(void* args)
{
TData* td = static_cast<TData*>(args); // 安全强制转换
while(true)
{
std::cout<<"name:"<<td->TName<<std::endl;
std::cout<<"create time:"<<td->CreatTime<<std::endl;
td->func();
sleep(1);
}
return nullptr;
}
创建参数包,创建子进程
int main()
{
pthread_t tid;
TData* TD = new TData("thread 1", (uint64_t)time(nullptr), Print);
pthread_create(&tid, nullptr, TRountine, TD);
while(true)
{
std::cout<<"这里是主线程"<<std::endl;
sleep(3);
}
return 0;
}
手动创建多线程的话,我们可以使用vector对pthread_t进行保存即可
需要注意的是pthread_t和LWP是不同的,除了使用pthread_create里面的第一个输出型参数进行获取,还可以使用pthread_self获取自己的线程id
这个pthread_t的id的本质其实是一个地址
在多线程情况下,一个线程出现中断异常,整个进程都会退出,因为CPU不认识线程!每一种信号的处理方式都是共享的
线程终止
第一种线程终止的方式就是当新线程把自己的有效代码运行完成之后,他就会自动终止,和子进程的逻辑是相同的
如果直接调用exit退出的话,整个进程会直接退出
除此之外我们可以调用pthread_exit让新线程退出
这里传递的参数我们直接传nullptr即可
线程等待
线程默认是需要被等待的
当线程退出,没有被等待,就会导致类似进程的僵尸问题
我们可以用pthread_join来等待线程退出
例如可以这样使用
pthread_join(tid, nullptr);
效果就类似于waitpid
成功返回0,失败返回错误码
获取的线程返回值是通过第二个参数传递出来的,因为create规定的函数指针类型返回值是void*,因此想要获得这个返回值,就要传递void**进去
那么返回值也可以设计一个类了
线程分离
因为线程出错了其实是影响到整个进程的,所有返回值的意义其实不大,那么要让主线程一直等待就是不合算的事情了,所以就有了线程分离
int pthread_detach(pthread_t thread);
我们可以这样让当前线程和主线程脱离关系pthread_detach(pthread_self());
pthread线程库
线程ID的本质是一个地址,而pthread是一个动态的第三方库
当我们进行编译运行的时候,这个库就会被映射到进程地址空间的共享区里
而进程ID的地址本质就是pthread这个库里,线程集合的其实地址
线程需要维护自己的战区,其实就是pthread来维护的