Linux操作系统6- 线程1(线程基础,调用接口,线程优缺点)
上篇文章:Linux操作系统5- 补充知识(可重入函数,volatile关键字,SIGCHLD信号)-CSDN博客
本篇Gitee仓库:myLerningCode/l27 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)
目录
一. Linux中的线程
1.1 线程的概念
1.2 OS管理线程的方式
1.3 线程与进程
二. Linux中创建线程 ⭐
1.1 pthread_create系统调用
1.2 线程之间的资源共享
1.3 线程的私有资源⭐
三. 线程的优缺点 ⭐
3.1 线程的优点
3.2 线程的缺点
一. Linux中的线程
1.1 线程的概念
进程是OS调度的基本单位,进程 = PCB + 进程对应数据与代码。而线程是进程内的一个执行流。
对于一个进程来说,进程是通过虚拟内存与页表的映射来访问物理内存的。所以一个进程的虚拟内存决定了这个进程所拥有的资源。
在linux中,对于一个进程,如果定义多个PCB指向同一个虚拟内存。让后让这些PCB指向不同的物理内存区域,这些PCB就是线程。
通过虚拟内存和页表的映射,我们对一个进程的资源进行了划分,这样一来,单个PCB的粒度就比整个进程粒度低。
关系如下图:
其中,所有的PCB + 进程虚拟内存 + 页表 + 进程对应数据代码 = 一个进程
单个PCB + 单个PCB占用的虚拟内存 + 页表 + 线程对应数据代码 = 一个线程
1.2 OS管理线程的方式
OS通过PCB来管理进程,同样所有的线程也需要被OS管理。
如果想要管理线程,首先需要设计特定的内核数据结构来表示线程对象,即设计一个TCB(线程控制块)。
Windows就是这样设计线程的。不过需要重新设计一个内核数据结构太麻烦了!
线程和进程有很多资源是一样的,比如id,状态,优先级,上下文,页表,虚拟内存,文件描述符...
因此Linux工程师使用直接复用进程的PCB,使用PCB来表示线程。而cpu只关心PCB不关心你是线程还是进程。
所以,线程在进程的地址空间中运行,拥有该进程一部分虚拟内存和资源。
1.3 线程与进程
进程是承担系统分配资源的基本单位(PCB,虚拟内存,页表,代码和数据等资源)
而线程是CPU调度的基本单位,在linux中称为轻量级进程\
总结:
1 在Linux中,没有严格的线程,只有轻量级进程,使用进程来模拟线程。
2 线程是cpu调度的基本单位,进程是系统分配资源的基本单位
linux使用轻量级进程模拟线程的好处:降低维护成本,提高效率
linux使用轻量级进程模型线程的坏处:用户只认线程,不认进程,所以linux需要为轻量级进程和线程之间提供线程库
二. Linux中创建线程 ⭐
1.1 pthread_create系统调用
使用man手册查看如下:
可以看到,使用这个接口需要在编译的时候带上参数 -pthread
//头文件
#include <pthread.h>
//库名称
pthread
int pthread_creat(pthread_t *thread, const pthread_attr_t *attr, void* (*start_routine)(void*), void *arg)
//参数说明
thread 用于定义线程tid的地址,是输入输出型参数,调用后tid获取线程的id
attr 用于控制线程的属性,我们一般设置为0.表示默认属性
start_routine 线程运行函数的指针,即线程的执行流函数。调用后线程就执行该函数内部的代码
arg 线程函数start_routine的参数
测试代码:
makefile文件
test:test.cpp
g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
rm -rf test
在Linux中,使用线程必须要使用-l pthread用于链接原生线程库
test.cpp
#include <iostream>
#include <unistd.h>
#include <pthread.h>
void *start_routine(void *args)
{
const char *name = static_cast<const char *>(args);
while (true)
{
std::cout << "我是新线程,我的名字是" << name << "pid为:" << getpid() << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid; // 定义线程tid
pthread_create(&tid, nullptr, start_routine, (void *)"I am thread"); // 创建线程
while (true)
{
std::cout << "我是主线程,我的pid为:" << getpid() << std::endl;
sleep(1);
}
return 0;
}
测试结果如下:
可以看到,主线程和新线程是并发执行的。
并且使用 ps axj 命令查看只有一个test进程,二使用 ps -aL查看线程可以看到test进程内有两个线程,它们的pid是由于的而LWP是不一样的!
CPU调度的时候,使用LWP来标识每一个线程。并且以进程中的主线程的LWP就是这个进程的pid。
1.2 线程之间的资源共享
线程被创建后,会共享全局变量,函数等资源。
测试代码:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
// 全局变量
std::string name = "线程基础";
// 全局函数
inline void f()
{
std::cout << "调用全局函数" << " 读取全局变量:" << name << std::endl;
}
void *start_routine(void *args)
{
const char *name = static_cast<const char *>(args);
while (true)
{
std::cout << "我是新线程,我的名字是" << name << "pid为:" << getpid();
f();
sleep(1);
}
}
int main()
{
pthread_t tid; // 定义线程tid
pthread_create(&tid, nullptr, start_routine, (void *)"I am thread"); // 创建线程
while (true)
{
std::cout << "我是主线程,我的pid为:" << getpid();
f();
sleep(1);
}
return 0;
}
测试结果:
可以说明:线程能够调用全局函数,也可以读写全局变量。这说明两个线程之间如果想要进行通信是非常简单的。不像进程间通信需要通过OS创建资源,然后让两个进程看到同一份资源
线程之间共享 虚拟内存的代码区,全局变量区,堆区,文件,信号处理
1.3 线程的私有资源⭐
线程之间共享全局资源,每一个线程也有着自己独立的资源。
1 线程是CUP调度的基本单位,每一个线程有着自己的PCB,有自己独立的id,优先级
2 线程有着自己的上下文结构,这体现线程切换的动态属性。上下文是指线程切换的时候,cpu寄存器中的值,PCB的状态,PCB对应的堆栈结构。
3 每一个线程都有自己独立的栈结构。这个栈结构位于共享区
三. 线程的优缺点 ⭐
3.1 线程的优点
1 线程的创建消耗比进程低很多
2 线程切换与进程切换相比,需要OS工作更少,消耗低
为什么线程切换比进程切换开销小?
1 进程切换需要切换进程的虚拟内存,页表,切换所有的PCB和上下文
2 线程切换只需要切换一个PCB和线程自己的上下文
3 线程切换不太用更新catch,进程切换需要更新所有的catch。catch是高速缓存,CPU通过catch与内存交流,一个稳定的进程,会缓存很多热点数据,这样一来,catch的命中率高,线程切换不怎么影响catch的命中率。
如果切换了进程,需要全部更新catch,这个需要消耗较多的时间
3.2 线程的缺点
1 如果创建的线程过多,会造成一定的性能损失
2 线程会导致代码的健壮性降低,因为一个线程异常退出了,会导致整个进程退出,线程崩溃了,整个进程会崩溃
3 线程之间共享资源,需要考虑访问控制和同步互斥问题