Linux 线程(上)

发布于:2025-05-18 ⋅ 阅读:(23) ⋅ 点赞:(0)

前言:大家早上中午晚上好!!今天来学习一下linux系统下所谓的线程吧!!!

一、重新理解进程,什么是进程?

1.1 图解

其中黑色虚线部分一整块就是进程,注意:一整块指的是:①:从一个程序运行起来后成为进程,OS堆这个进程进行描述PCB结构、②这个PCB中的进程地址空间所映射的物理内存资源、③页表,等等。。。总而言之:所有保证这个进程正常运行的所有资源!!

也就是说:进程是系统资源分配的基本单位,每一个进程的资源由系统分配,分配出来后的整一块资源都属于进程,进程相当于大家庭

1.2、Linux系统下的线程

图解:

图中通过第一个创建的PCB描述出来的PCB2、PCB3、PCB4、PCB5暂时可以把它理解为线程!!(注意:只是暂时把它认为是线程);

这些通过主线程描述出来的PCB2、3、4、5.....等他们共同瓜分进程地址空间,而这个地址空间就是资源的窗口(通过页表跟物理内存建立一一映射关系),所以相当于同一进程下所有线程在同一块资源下进行瓜分,然后这些线程就可以分别执行自己的任务!!!(所以线程相当于家庭中的每一个成员:爸爸、妈妈、爷爷、奶奶、哥哥、姐姐、等!!)

1.3线程的调度

创建并完成资源分配后,cpu就可以对一个个PCB进行调度了,让他们分别执行代码中的某一部分内容,各自完成自己的任务!!所以:线程是cpu调度的基本单位

二、pthread库

2.1、pthread库是什么先不管,先用,先写一段代码

写一段代码实现创建一个线程,并让它执行自己的任务:

写之前先介绍一个创建线程的接口:

首先第一个参数为:输出型参数,需要自己定义pthread_t 类型变量,把这个变量地址传入,创建完成后获取到pthread_t;

第二个参数:线程属性,不需要修改设置为nullptr即可;

第三个参数:线程的入口函数,这个线程一被调用就会从入口函数开始执行,这个函数自己定义,这个函数的返回值是void*类型,参数为void*类型;

第四个参数:给入口函数传送的形参,如果不许要传设置为nullptr;

创建完线程,线程跑完后要对线程进程回收;

介绍一个等待线程的接口:

第一个参数:为线程的tid,传入哪个tid就等待哪个线程;

第二个参数为:获取这个线程的退出信息,如果不需要设置为nullptr;

返回值int:如果等待成功返回0,如果失败返回错误码;

有了这两个接口我们就来写一段代码吧:

#include <iostream>
using namespace std;
#include <pthread.h>
#include <unistd.h>
void* print(void* args)
{
    int cnt=10;
    while(cnt--)
    {
        cout<<"i am a thread !!!!"<<endl;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,print,nullptr);//线程创建
    int cnt2=10;
    while(cnt2--)
    {
        cout<<"i am a main thread!!!!"<<endl;
        sleep(1);
    }

    int n=pthread_join(tid,nullptr);//线程等待
    if(n==0)cout<<"wait success!!!"<<endl;
    return 0;
}

当我们编译的时候发现报错:

原因是因为pthread.h是动态库,我们编译的时候需要加上 -lpthread 才能链接成功:

makefile:

mythread:mythread.cc
	g++ -o $@ $^ -std=c++11 -lpthread   //编译时加上-lpthread
.PHONY:clean
clean:
	rm -f mythread

编译成功并生成mythread可执行程序:

运行:

我们看到主线程和分线程分别打印了10次,并成功等待了线程;

2.2 批量创建线程

上面我们创建的是一个线程,如果我们想批量创建线程呢?

那很简单,用一个for循环创建线程同时把tid保存起来,再用一个for循环等待tid:

同时我们每个线程获取一下pid_t(进程pid),同时每个线程获取一下pthread_t(tid),同时用轻量级命令 :ps- aL 查看所有轻量级进程:

#include <iostream>
using namespace std;
#include <pthread.h>
#include <unistd.h>
#include <vector>
#define THREAD_NUM 5
void* print(void* args)
{
    
    cout<<"i am a thread !!!! tid is :"<<pthread_self()<<"  pid is:"<<getpid()<<endl;
        sleep(1);
    return nullptr;
}
int main()
{
    vector<pthread_t> tids;
    //批量创建线程并保存tid
    for(int i=0;i<THREAD_NUM;i++)
    {
    pthread_t tid;
    pthread_create(&tid,nullptr,print,nullptr);
    tids.push_back(tid);
    }


    for(int i=0;i<THREAD_NUM;i++)
    {
    int n=pthread_join(tids[i],nullptr);//线程批量等待
    if(n==0)cout<<"wait success!!!  tid is : "<<tids[i]<<endl;
    }
    return 0;
}

编译后运行:

同时命令行输入ps - aL:

我们发现所有线程它的pid 都是相同的,tid是一串很长的数字且都是不相同的,用ps -aL命令查看到LWP中只一个数字15304是跟pid一样其他都不相同!!

pthread_t tid 究竟是什么?LWP是什么?他们跟PID之间的关系是什么?还有pthread库又是什么?

2.3重新理解线程

①:在linux系统下一般把线程称为轻量级进程,因为在Linux内核中没有用新的数据结构来描述线程,而是通过原有的进程PCB来描述线程的!!

②: 因此linux系统是通过LWP来对这些所谓的 “线程” 进行调度的,LWP就是:light weight process (轻量级进程的意思),所以在linux下所谓的线程:就是一个个通过(第一个创建出来的进程的PCB)描述出来的轻量级进程!

③: 那么linux系统不创建新的数据结构来描述线程,等于linux不想管理这些线程,那么谁来管理?pthread库!!来了!pthread库是第三方库,当我们安装linux操作系统的时候必须把pthread库安装好,这个pthread库里有别人编写好的一系列对线程操作的接口,且这个库需要管理每一个用户创建出来的线程!!!怎么管理?->先描述再组织!->pthread库必须有一个描述线程的数据结构(TCB)->pthread库通过对一个个TCB的管理从而管理一个个线程!!

④线程由pthread库管理,pthread库不属于OS内核,所以线程不属于OS内核!!在linux下所谓的线程是用户级线程!!

当pthread库调用一系列的pthread_create、pthread_join、等接口时其底层调用的其实是clone这个系统调用接口:

图解:

所以我们可以回答上面的第一个问题了:pthread_t tid 究竟是什么:

pthread_t tid 是用户级线程的tid!!所谓用户级就是管理线程的pthread库属于用户级!!每一个TCB有其对应的tid,用户通过tid便可以指定对某个线程进行操作!!

然后我们还能回答第二个问题了:LWP是什么:

图解:

我们知道每个PCB都有它的PID,linux下的“线程”是通过进程的PCB描述出来的;

当它被描述出来之后就是一个所谓的“线程”了,也就是称为了cup调度的基本单位

而PID代表的是进程,所谓进程代表的是被分配到的整块资源所有“线程”共享的这一个块资源

因此所有的“线程”的PID只有一个!!这些新创建出来的“线程”所以用LWP来标志!!

如果LWP跟PID一样说明这是第一个被创建出来的“线程”,即主线程!!!

最后cup就能通过LWP调度到每个它想调度的“线程”!!

同时我们可以回答第三个问题:pthread库是什么:

因为linux内核不管理线程,所以linux中把线程称之为:轻量级进程(它管理的是进程);

因为线程需要被管理,所以linux必须安装有pthread库;

pthread库就是用来管理用户创建除了的一切线程;

既然要管理所以pthread库必须对线程进行描述,必须要有TCB结构,通过一个个TCB结构的管理实现对所有线程的管理;

因此Linux下的所谓线程其实是用户级线程,那么我们可以这么认为:Linux下的线程=LWP(linux内核中的PCB轻量级进程标志)+pthread_t TID(用户中的TCB线程的标志);

三、线程的控制

3.1 线程的分离

如果主线程不想等待分支线程,我们可以让线程自己分离,分离后由OS进行;

线程分离接口:

#include<pthread.h>
int pthread_detach(pthread_t thread);
//1. 这是用来分离线程的pthread库接口
//2. thread:你想分离的线程的tid
//3. 返回值int,如果分离成功返回0,如果分离错误返回错误码

获取当前线程tid接口:

#include <pthread.h>
pthread_t pthread_self(void);
//1. pthread_t 返回值如果获取成功返回当前线程的tid

代码:

我们分离的同时等待一下线程看看能否等待成功:

线程分离的两种方法:线程自己分离

#include <iostream>
using namespace std;
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cstring>
#define THREAD_NUM 5
void* print(void*args)
{
    pthread_detach(pthread_self());
    cout<<"i am a thread!!"<<endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,print,nullptr);
    sleep(1);//等待1s让线程分离完成
    int code=pthread_join(tid,nullptr);
    if(code==0)
    {
        cout<<"wait success !!"<<endl;
    }
    else
    cout<<"wait false!! error num is:" <<code<<"error message :"<<strerror(code)<<endl;
    return 0;
}

编译运行:

线程分离成功,主线程等待失败,返回22错误码,意思是无效的参数,也就是没有等待到对应的tid;

线程分离方法二:主线程让他分离:

编译运行:

结果一样,说明成功分离分支线程;

3.2 线程的退出

线程退出的几种方式:

①线程运行完走到return 正常退出;

②终止线程的接口:

#include<pthread.h>
void pthread_exit(void* retval);
//1. retval 可以传入退出信息,由外部线程等待函数获取

我们退出的同时可以传入参数retval让主线程获取退出信息:

主线程等待获取线程退出信息需要传入一一个二级参数,因为join的第二个参数是一个输出型参数:

void* print(void*args)
{
    cout<<"i am a thread!!"<<endl;
    pthread_exit((void*)100);
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,print,nullptr);
    sleep(1);
    void*retval;
    pthread_join(tid,&retval);
    (long long int)retval;
    printf("%d\n",retval);
    return 0;
}

编译运行:

100退出码被带了出来;

③还有一个线程会终止的场景:当同一线程组内的任一线程出现异常,接收到终止信号,那么整个进程会崩溃,所有线程都会退出;

3.3 线程的取消

主线程可以取消一个线程,前提是这个线程已经被启动:

取消线程的接口:

#include<pthread.h>
int pthread_cancel( pthread_t thread);
//1. 取消一个线程,前提这个线程被启动
//2. thread : 需要取消的线程的tid
//3. 如果成功返回0,失败返回一个错误码
void* print(void*args)
{
    while(true)
    {
    cout<<"i am a thread!!"<<endl;
    sleep(1);
    }
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,print,nullptr);
   int code= pthread_cancel(tid);
    if(code==0)
    {
    cout<<"取消成功"<<endl;

    }
    else
    cout<<"取消失败"<<endl;
    //sleep(1);//线程已被启动
    pthread_join(tid,nullptr);
    return 0;
}

运行:

四、总结线程的特点

4.1 、线程是系统调度的基本单位,进程是资源分配的基本单位;
4.2、线程的粒度小于进程,占用资源更少,因此通常多线程比多进程更高效;
4.3、线程没有独立的地址空间,线程是划分进程的地址空间,本质上使用的是同一地址空间!线程不拥有系统资源,线程共享进程的资源!
4.4、大量的计算使用多线程和多进程都可以实现并行/并发处理,区别在于线程的资源消耗小于进程,线程的稳定性不如进程,因此需要具体更细致的需求场景;
4.5、线程除了独立的栈空间和一组寄存器(保存线程的上下文)其他线程看不到之外其他所有资源都是所有线程共享的;
4.6、线程的通信速度更快,切换更快,因为他们在同地址空间内,且还共享了很多其他进程资源,比如页表指针,这些是不需要切换的!

今天的分享就到这里!如果对你有所帮助记得点赞收藏+关注哦!!谢谢!!!

咱下期见!!!


网站公告

今日签到

点亮在社区的每一天
去签到