Linux操作系统之线程(四):线程控制

目录

前言:

一、上文补充

二、线程终止

三、线程等待

四、线程的exec问题

总结:


前言:

上一篇文章我们着重对线程他的共享代码这个特点进行了论述,讲解了部分性质与容易出现的问题。

那么现在我们本篇文章就更加深层次的来学习一下线程吧!

一、上文补充

我们说线程的绝大部分资源都是共享的,这句话其实不是很完善。

最准确的说法,线程之间一切都是共享的。

有人说:不是还有独立的栈结构吗?

其实,栈结构的独立性是编程语言或操作系统提供的逻辑保护,而非物理隔离。他只是不想让你看见,所以说如果你非要看见,是有办法的:

cpp 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>

int *ptr=nullptr;
void* func1(void*_name)
{
    std::string name=static_cast<const char*>(_name);
    int a=100;//临时变量,在栈上面
    ptr=&a;
    while(true)
    {
        std::cout<<name<<"线程运行: "<<a<<std::endl;
        sleep(1);
    }
}

void* func2(void *_name)
{
    std::string name=static_cast<const char*>(_name);
    while(true)
    {
        if(ptr!=nullptr)//防止func2先运行此时ptr还为空直接越界访问
            std::cout<<name<<"线程运行: "<<(*ptr)++<<std::endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,nullptr,func1,(void*)"pthread-1");
    pthread_create(&tid2,nullptr,func2,(void*)"pthread-2");
    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);
    return 0;
}

可以看见这个代码中,我们在func一的线程中初始化了一个局部变量,但是我们可以通过上节课讲的共享的特性,用全局变量指针,来让多个线程之间看到同一份资源。


二、线程终止

我们之前一开始就给大家讲解了线程的创建,大家也就知道了在linux中严格意义上来说是没有线程的概念的,只有轻量级进程。

而我们的pthread_create函数内部其实是封装了clone,clone这个函数实际上是用来创建轻量级进程的。为了产生线程这个概念,我们就在轻量级进程上面进行了一层封装,这也就是pthread_create的由来。

而pthread_create这个函数被各大语言封装成了各大语言(C++,java)线程接口,但是由于底层是pthread_create,所以我们在编译时都要链接上我们的pthread库。

回顾完我们线程创建的知识,现在我们来了解一下线程退出的几个方法:

我们手动给主副线程最后结束返回return可以知道,主线程使用return会导致所有线程都退出,也就是理论上的进程退出了。而副线程的return只会导致副线程退出。(这就跟我们调用了一个函数return返回一样,不会影响main函数)

那么exit呢?这个函数我们在讲进程退出时说过,这个函数会导致进程之间退出。那么大家就可以预料到了,无论是在主线程还是副线程中调用exit,都会导致所以线程立马退出,也就相当于进程退出。

正如同进程有专门的exit函数来退出,线程也有专门的函数调用来退出线程:

pthread_exit函数的作用就是终止调用它的线程,并可选地返回一个值(线程的退出状态)。

cpp 复制代码
void* func(void* arg) 
{
    printf("子线程正在运行\n");
    pthread_exit((void*)42); // 终止线程并返回值42
}

int main() 
{
    pthread_t tid;
    void* retval;
    pthread_create(&tid, NULL, func, NULL);
    pthread_join(tid, &retval); // 获取子线程的退出值
    printf("子线程返回: %ld\n", (long)retval); // 输出42
    return 0;
}

值得一提的是,在多线程编程中,当线程通过 pthread_exitreturn 返回一个指针时,必须确保该指针指向的内存是全局变量或堆内存(malloc 分配),而不能是线程栈上的局部变量。这是因为线程栈的生命周期与线程绑定,线程退出后,其栈内存会被回收,导致返回的指针指向无效内存(悬垂指针),引发未定义行为(如数据损坏或程序崩溃)。


除了这个函数来退出线程之外,我们还可以取消线程:

pthread_cancel 是 POSIX 线程库中用于请求取消另一个线程的函数。它的作用类似于向目标线程发送一个"终止请求",但具体是否终止、何时终止以及如何清理资源,取决于目标线程的取消状态和清理处理机制。

这个函数,通常是用我们的主线程调用,来取消副线程,取消的线程的返回值为-1。

cpp 复制代码
void*func(void* argv)
{
    while(true)
    {
        std::cout<<"子线程运行中 "<<std::endl;
        sleep(1);
    }
}


int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, func, nullptr);

    sleep(5);
    pthread_cancel(tid);
    void* ret=nullptr;
    pthread_join(tid, &ret);
    std::cout<<"子线程退出信息:"<<(long long int)ret<<std::endl;
    return 0;
}

这里我们要注意什么呢?首先,你要取消一个线程,这个线程必须要先被启动了。并且,被你取消的进程一定要用join回收,否则会导致资源泄漏。

所以,我们就一定要谨慎调用这个函数。


三、线程等待

我们之前已经讲过线程等待函数pthread_join,所以我们这里不再过多赘述,我在这里补充一些内容。

那如果我们的主线程要做自己事情呢?

我们之前说过,线程等待会阻塞进程。有没有什么办法让主线程不阻塞呢?

有的:我们可以切换线程等待的状态。

一个线程有两种被等待的状态:

1、joined:线程需要join(默认状态)

2、detach:线程分离(主线程不必等待副线程)

我们可以调用pthread_detach函数使一个副线程的状态变为detach分离状态:

的作用是告诉操作系统:当该线程结束时,系统自动回收其资源,无需其他线程调用 pthread_join来等待它 。线程结束后,系统自动回收其资源,其他线程无法再调用 pthread_join等待它(调用会失败)。

但是这也会给我们带来一些问题:如果我们的主线程先退出了,副线程还没退出,并且副线程此时是分离状态呢?这会导致副线程直接退出(大部分系统下)

所以我们一定要保证主线程比副线程后退出,如果不能保证,就不要分离线程。

四、线程的exec问题

我们想问一下,当我们新建了一个线程之后,还能进行exec吗?此时exec会造成什么后果呢?

exec 会 完全替换当前进程的地址空间,包括所有线程(无论是否分离),而线程共享同一个进程地址空间,所以其他线程的执行会被强制中断,且 没有机会执行清理操作。

所以我们不能使用exec的调用接口。

那如果我们想使用exec函数,该怎么办呢?

答案是:fork!!

没错,就算是在副线程中,我们也可以调用fork接口,创造一个新进程!!

随后,就能在这个进程中使用exec的调用接口了。

当我们在副线程中调用fork,他只会复制当前线程。也就是说,新创建的进程内只会有一个PCB。

总结:

兄弟们,线程这玩意儿就是个共享怪胎,表面上说栈是独立的,但实际上我用个全局指针就能偷看其他线程的栈数据!创建线程底层就是个clone系统调用,各大语言都是套了层皮而已。

线程退出的姿势也可多:主线程return直接带崩全场,副线程return就跟函数返回一样乖巧。pthread_exit能优雅退场还能留个遗言,但记住别返回局部变量的地址,不然分分钟给你来个悬垂指针的惊喜!

pthread_cancel这货就是个线程杀手,一枪崩了目标线程,但记得一定要用join收尸,不然资源泄漏有你受的。如果想不阻塞主线程?detach一下就行,但主线程要是先溜了,子线程直接凉凉。所以要保证退出顺序哦~

最坑爹的是exec,这玩意儿一调用,管你几个线程统统完蛋!想用exec?先fork个新进程再说。不过fork也是个坑货,只复制当前线程,其他线程的锁啊资源啊全都不管了,死锁警告!

总之,玩线程就是在刀尖上跳舞,一个不小心就翻车!

希望对你们有用!

相关推荐
乌萨奇也要立志学C++2 分钟前
【Linux】权限详解 权限本质、权限属性、su、sudo提权、chmod\chown\chgrp、文件类别
linux
uwvwko32 分钟前
安装kali时出现“安装步骤失败“如何解决及后续软件安装
linux·运维·服务器·安全·kali·ctf
黄金旺铺1 小时前
ubuntu 清理脚本
linux·运维·ubuntu
ZZZKKKRTSAE1 小时前
秒赤Haproxy配置&算法
linux·运维·服务器·网络·haproxy
少年攻城狮2 小时前
Neo4j系列---【Linux离线安装neo4j】
linux·运维·服务器·neo4j
Molesidy2 小时前
【Ubuntu】Ubuntu的分区深入详解以及制定最佳分区分配策略
linux·ubuntu·分区
起风了啰2 小时前
linux服务器配置mihomo
linux
青竹易寒12 小时前
Linux命令技术笔记-sed+awk命令详解
linux·运维·服务器
Kiri霧13 小时前
Kotlin泛型约束
android·linux·windows·kotlin