【Linux】线程

文章目录

线程(Thread)

1. 什么是线程?

线程是进程中的一个执行单元,它是 CPU 调度的基本单位 。线程依赖于进程存在,一个进程可以包含多个线程,这些线程可以并发执行,提高程序的运行效率。

进程是承担系统分配系统资源的实体

线程是操作系统调度的基本单位

用一张图简要说明一下什么是线程:

首先我们要知道,在Linux中是没有实际的线程的,线程是被模拟出来的,Linux实际上使用LWP模拟的线程。
LWP(Light Weight Process,轻量级进程)是 Linux 线程实现的一种机制,它与传统进程共享大部分资源,但仍有自己的调度信息。

创建线程

pthread_create是用于创建线程的函数,这个函数不是系统调用,因为Linux实际上是没有实体的线程,这个创建线程的函数是在pthread.h中封装的函数。

这个函数的第一个参数是pthread_t*类型的,pthread_t表示线程id,第二个参数表示线程属性,要是我们不观星的话,就直接传递nullptr,第三个参数是线程执行函数,就是函数指针,新线程就是从这个函数的入口开始执行的,第四个参数是传递给执行新线程的函数的参数。

代码展示:

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;


void *routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        cout << "I am new thread,the name is:" << name << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t id;
    pthread_create(&id,nullptr,routine,(void*)"thread-1");
    while(true)
    {
        cout << "I am main thread" << endl;
        sleep(1);
    }
    return 0;
}

如果真的创建了一个新线程,那么routine函数也会跑起来,这里有两个执行流。

可以看见确实有两个执行流。

创建线程之后,我们来讨论一下这个id是什么,没错就是线程id,我们打印一下:

可以看到转化为16进制之后打印出来的是这个,如何用函数获得线程id呢?在库中有一个函数可以获取线程的id:

这个函数不用传任何参数,可以获取本线程的id,我们来测试一下这个函数的正确性:

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

//转化为十六进制
string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}

void *routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        cout << "I am new thread,the name is:" << name <<",this thread's id is:" << toHex(pthread_self()) <<endl;
        sleep(1);
    }
}

int main()
{
    pthread_t id;
    pthread_create(&id,nullptr,routine,(void*)"thread-1");
    cout<<toHex(id)<<endl;
    while(true)
    {
        cout << "I am main thread" << endl;
        sleep(1);
    }
    return 0;
}

可以看到结果是一致的。

我们现在已经创建了一个新的线程,意思就是我们现在有两个线程,我们来查一下:

不加任何选项查线程只能查出进程。

cpp 复制代码
ps -aL

可以看见确实有两个线程,两个线程的pid是相同的,那哪一个是主线程,哪一个是新线程呢?pid和lwp相同的是主线程,pid和lwp不同的是新线程。

多线程中的重入问题

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

//转化为十六进制
string toHex(pthread_t tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);
    return buffer;
}

void *routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        cout << "I am new thread,the name is:" << name <<",this thread's id is:" << toHex(pthread_self()) <<endl;
        sleep(1);
    }
}

int main()
{
    pthread_t id1;
    pthread_create(&id1,nullptr,routine,(void*)"thread-1");
    pthread_t id2;
    pthread_create(&id2,nullptr,routine,(void*)"thread-2");
    pthread_t id3;
    pthread_create(&id3,nullptr,routine,(void*)"thread-3");
    pthread_t id4;
    pthread_create(&id4,nullptr,routine,(void*)"thread-4");
    while(true)
    {
        cout << "I am main thread" << endl;
        sleep(1);
    }
    return 0;
}

我们可以同时创建多个线程:

我们运行之后,可以看见,有时候显示器屏幕上会出现混乱的现象,这是因为当我们创建多个线程的时候,这多个线程同时访问routine函数,routine函数就被重入了,因为routine函数中有cout这个io函数,这个函数的本质其实是在访问显示器文件,在向显示器文件上写入,所以这里显示器文件是公共资源,被多个线程访问,因为这里的公共资源没有加上保护,所以每个线程想怎么打印就怎么打印,所以有时候会出现混乱的现象。

线程异常

当某一个线程出现野指针或者除0等错误异常时,整个进程都会退出,这是因为,当某一个线程触发异常时,在CPU当中会触发中断,触发中断后,会拿着中断去找处理方法,找到之后,就会处理信号,因为信号的处理单位时进程,所以处理信号的时候会直接改写每个线程的信号表,将每个线程直接杀死。

线程等待

不光是进程需要等待,线程也是需要等待的,线程和进程一样,默认等待方式时阻塞等待。

线程等待函数:

第一个参数是线程的id,第二个参数是二级指针,如果我们不关心退出结果,那么我们可以直接将第二个参数设置为nullptr。

cpp 复制代码
void *routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        cout << "I am new thread,the name is:" << name <<",this thread's id is:" << toHex(pthread_self()) <<endl;
        sleep(1);
        break;
    }
}

int main()
{
    pthread_t id1;
    pthread_create(&id1,nullptr,routine,(void*)"thread-1");

    pthread_join(id1,nullptr);
    return 0;
}

我们让新线程跑一秒钟,然后跑完直接退出,看看会是什么结果。

可以看见跑完一秒钟后直接就退出了。

假如我们关心退出结果呢?

我们打印一下退出结果,可以看见,退出结果就是新线程的返回值。

这里我们可以来通过返回值验证一下堆空间是线程之间共享的。

cpp 复制代码
void *routine(void* args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        cout << "I am new thread,the name is:" << name <<",this thread's id is:" << toHex(pthread_self()) <<endl;
        sleep(1);
        break;
    }
    int *p = new int(10);
    return (void*)p;
}

int main()
{
    pthread_t id1;
    pthread_create(&id1,nullptr,routine,(void*)"thread-1");
    void* ret = nullptr;
    pthread_join(id1,&ret);
    cout<<*(int*)ret<<endl;
    return 0;
}

这类我们在新线程中new一个空间,线程退出之后,主线程是可以访问到new出的地址的,new是在堆空间中开辟的,所以这里可以看出堆空间是共享的,其实如果我们想的话,栈空间也是可以共享的,只需要在新线程中创建一个int变量,然后创建一个全局变量,int*的,然后然后在新线程中,把那个变量的地址给全局变量,其实在主线程中,我们是可以修改这个变量的。所以也验证了堆空间,虽然没办法共享,但是也是可以想办法看到的。

总结

在本篇文章中,我们深入探讨了线程的基本概念、创建方式、多线程中的重入问题、线程异常处理以及线程等待机制。通过这些内容,我们可以更好地理解线程的工作方式,并在实际开发中合理运用多线程技术,提高程序的并发性能。

多线程编程虽然能够提升程序的执行效率,但也伴随着诸多挑战,如数据竞争、死锁、线程安全等问题。因此,在使用多线程时,我们需要充分考虑同步与互斥机制,合理设计程序结构,以确保线程安全性和稳定性。

希望本篇文章能帮助你更好地理解多线程的基础知识,并在实践中灵活应用。如果你有更多问题或想深入学习,可以继续探索线程池、锁优化、异步编程等更高级的内容。

相关推荐
wdxylb19 分钟前
云原生俱乐部-shell知识点归纳(1)
linux·云原生
飞雪20071 小时前
Alibaba Cloud Linux 3 在 Apple M 芯片 Mac 的 VMware Fusion 上部署的完整密码重置教程(二)
linux·macos·阿里云·vmware·虚拟机·aliyun·alibaba cloud
路溪非溪2 小时前
关于Linux内核中头文件问题相关总结
linux
海绵不是宝宝8173 小时前
连接远程服务器上的 jupyter notebook,解放本地电脑
服务器·jupyter·github
Lovyk4 小时前
Linux 正则表达式
linux·运维
Fireworkitte5 小时前
Ubuntu、CentOS、AlmaLinux 9.5的 rc.local实现 开机启动
linux·ubuntu·centos
sword devil9006 小时前
ubuntu常见问题汇总
linux·ubuntu
ac.char6 小时前
在CentOS系统中查询已删除但仍占用磁盘空间的文件
linux·运维·centos
繁星¹⁸⁹⁵7 小时前
通过update-alternatives可以实现cuda的多版本切换
服务器
淮北也生橘127 小时前
Linux的ALSA音频框架学习笔记
linux·笔记·学习