【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*的,然后然后在新线程中,把那个变量的地址给全局变量,其实在主线程中,我们是可以修改这个变量的。所以也验证了堆空间,虽然没办法共享,但是也是可以想办法看到的。

总结

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

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

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

相关推荐
tan180°13 分钟前
版本控制器Git(4)
linux·c++·git·后端·vim
IT 古月方源38 分钟前
linux centos 忘记root密码拯救
linux·运维·centos
AORO_BEIDOU1 小时前
防爆手机如何突破“安全与效率“悖论?解析AORO M8的双驱动创新
网络·人工智能·科技·5g·安全·智能手机·信息与通信
落幕2 小时前
在线商城服务器
linux·服务器·c语言
小袁搬码2 小时前
docker引擎与docker-compose离线版本下载详细教程
linux·运维·docker·容器·docker-compose
努力犯错玩AI3 小时前
生产环境H200部署DeepSeek 671B 满血版全流程实战(三):SGLang 安装详解
linux·后端·python
IYU_3 小时前
文件解析漏洞靶场通关合集
服务器·web安全·网络安全
海绵波波1073 小时前
【运维】服务器系统从centos7重装为ubuntu22.04
运维·服务器
roboko_3 小时前
Linux网络套接字编程——UDP服务器
linux·服务器·网络
网络安全指导员3 小时前
网络安全设备系统集成方案 系统集成和网络安全
linux·开发语言·网络·安全·web安全·php