Linux 线程控制

news2025/2/28 18:12:50

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:Linux初窥门径⏪

🚚代码仓库:Linux代码练习🚚

🌹关注我🫵带你学习更多Linux知识
  🔝 

目录

 前言

1.线程现象

2.线程等待

3.线程终止 

4.重新理解线程


 

 前言

在Linux 线程概念 ​​​​​​ 中 我们知道Linux中的线程是轻量级的,这就意味着Linux中是没有线程这种概念的。所以用户要想用线程,OS不会提供,Linux程序员在应用层给我们提供了第三方库 pthread库

在线程概念章节中,最后简单演示了创建线程的代码,这里就有个问题了?一个进程中什么是主线程,一个线程要被调度,cpu怎么知道该调度谁了?

1.线程现象

最后线程概念简单演示了创建线程。

简单了说了 LWP 就是轻量级进程 也是线程的标识符

 cpu通过LWP调用线程。 如果一个线程的LWP和PID的值是一样的。说明该线程就是主线程。

如果一个线程的LWP和PID的值不相等说明它是被新创建出来的线程。

那以前CPU调度进程是错的吗? 当然不是,以前我们学的是进程中单线程。所以给人感觉是CPU调度进程。说白了,我们以前学的进程单线程只是线程的子集。

 这里代码和pthread_create函数在线程概念有详细解释Linux 线程概念 

 那如果 我们给其中一个线程发送线程会怎么样?

 从上面结论我们可以得出 线程中只要有一个线程出现异常,那么其他线程都要被干掉。

讲个故事

进程就好比家庭,线程就好比家庭的成员。 家庭的成员各自干各自的事。爸妈上班,你上学,你爷爷奶奶安享晚年。大家干各自的事都是一个目的。让日子过的更好。

如果你在学校出了问题,老师是不是要请你爸妈来学校一趟,所以他们就会放下手中的工作。来学校处理你的事情。

所以一个线程出了问题,其他线程也会跟着出问题。

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void show(const string &name)
{
    cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{
    while (true)
    {
        cout << "new thread , pid: " << getpid() << endl;
        show("[新线程]");
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);
    while (true)
    {
        cout << "main thread , pid: " << getpid() << endl;
        show("[主线程]");
        sleep(1);
    }
    return 0;
}

这段代码运行结果来看, 两个线程同时都在执行函数show,说明show函数被重入了

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;

void *threadRoutine(void *args)
{
    while (true)
    {
        
       printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);
    while (true)
    {
        
       printf("main thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
        g_val++;
        sleep(1);
    }
    return 0;
}

         

我们定义一个全局变量,可以看到两个线程都可以同时访问这个全局变量。主线程++ 新线程打印的值也是++后的值。地址也是一样。

这就说明一个问题 代码未初始区,已初始区。都是共享的。

那两个线程通信岂不是非常容易?

是的。 

2.线程等待

一个进程中,能确定那个线程先运行吗?这个还真的不能确定是主线程先运行还是新线程先运行。

但是我们能确定主线程一定是最后退出的。为什么?

因为新线程就是主线程创建出来的。主线程如果不等待新线程那么就会出现类似父子进程中的僵尸进程情况。造成资源浪费。

而原生线程库中帮我们提供了线程等待的接口函数。pthread_join 

老规矩先来了解函数原型、参数、返回值。

pthread_join - 等待一个已终止的线程

函数原型 

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

同样和pthread_create一样,需要在编译时链接-pthread选项

描述

pthread_join()函数用于等待由thread参数指定的线程终止。如果该线程已经终止,则pthread_join()会立即返回。被指定的线程必须是可加入的(joinable)。

如果retval参数不是NULL,则pthread_join()会将目标线程的退出状态(即目标线程传递给pthread_exit(3)的值)复制到*retval指向的位置。(说人话就是**retval这个参数设置为空,不关心线程的退出情况

如果目标线程被取消,则在*retval中放置PTHREAD_CANCELED

如果多个线程同时尝试加入同一个线程,则结果未定义。如果调用pthread_join()的线程被取消,则目标线程将保持可加入状态(即不会被分离)。

返回值

成功时,pthread_join()返回0;出错时,返回一个错误编号。

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;
void show(const string &name)
{
    cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{
    int cnt = 5;
    while (true)
    {
        // cout << "new thread , pid: " << getpid() << endl;
        // show("[新线程]");
        printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
        cnt--;
        sleep(1);
        if(cnt == 0) break;
    }
    
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);
    pthread_join(tid, nullptr);
    cout << "main thread quit..." << endl;
    return 0;
}

 

从运行结果来看,前4秒钟,主线程和新线程都是同时在跑的,第5秒新线程退出,最后才是主线程退出。这里我们可以得出一个结论:主线程在等待新线程退出的这个过程中,主线程其实是阻塞等待 

这里就有一个问题,新线程退出了,执行结果主线程是怎么拿到的?

为什么要传一个二级指针? 如果是传一个一级指针。pthread_join能拿到void*这个返回值吗?

就好比 int a = 0;函数能改变外面 a的值吗?肯定不能。 如果一个函数要改变a的值,那只能传递a变量的指针, 也就是所谓 int*。 这里void*就如同a变量。,void** 就如同 int* 。

新线程返回的是void*的指针,pthread_join就要拿到 取地址void* ,通过解引用。最后就能拿到void*了。

那我们直接改造代码

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;
void show(const string &name)
{
    cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{
    int cnt = 5;
    while (true)
    {
        // cout << "new thread , pid: " << getpid() << endl;
        // show("[新线程]");
        printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
        cnt--;
        sleep(1);
        if(cnt == 0) break;
    }
    
    return (void*)1;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);
    // while (true)
    // {
    //     // cout << "main thread , pid: " << getpid() << endl;
    //     // show("[主线程]");
    //     printf("main thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
    //     g_val++;
    //     sleep(1);
    // }
    void* retval;
    pthread_join(tid, &retval);
    cout << "main thread quit..." << " " << " retval: "<< (long long int)retval << endl;
    return 0;
}
代码运行结果来看我们确实拿到了1。

 

3.线程终止 

在进程中进程退出用的exit()和 _exit() 那线程也能用吗?

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;
void show(const string &name)
{
    cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{
    int cnt = 5;
    while (true)
    {
        // cout << "new thread , pid: " << getpid() << endl;
        // show("[新线程]");
        printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
        cnt--;
        sleep(1);
        if(cnt == 0) break;
    }
    exit(1);
    return (void*)1;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);
    while (true)
    {
        // cout << "main thread , pid: " << getpid() << endl;
        // show("[主线程]");
        printf("main thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
        g_val++;
        sleep(1);
    }
    void* retval;
    pthread_join(tid, &retval);
    cout << "main thread quit..." << " " << " retval: "<< (long long int)retval << endl;
    return 0;
}

新线程退出主线程也跟着退出了,也就是说,线程只要有一个退出了,其他线程都要跟着退出,这种情况并不是我们想要的。 原生线程库也是帮我们提供线程终止的接口 pthread_exit

谁调用就会终止谁,其他线程不会受影响。

函数原型void pthread_exit(void *retval);

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;
void show(const string &name)
{
    cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{
    int cnt = 5;
    while (true)
    {
        // cout << "new thread , pid: " << getpid() << endl;
        // show("[新线程]");
        printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
        cnt--;
        sleep(1);
       
        if(cnt == 0) break;
    }
    pthread_exit((void*)100);
    //exit(1);
    //return (void*)1;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);
    // while (true)
    // {
    //     // cout << "main thread , pid: " << getpid() << endl;
    //     // show("[主线程]");
    //     printf("main thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());
    //     g_val++;
    //     sleep(1);
    // }
    void* retval;
    pthread_join(tid, &retval);
    cout << "main thread quit..." << " " << " retval: "<< (long long int)retval << endl;
    return 0;
}

结果如我们所预期的一样我们拿到线程回调函数的返回值。但是回调参数是void*,这意味什么?意味着我们不仅仅只能传递字符常量,函数指针,整型、等等。我们还可以传递对象啊。下面就来一个优雅的写法!!!

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>

using namespace std;
class Response
{
public:
    Response(int exitcode, int result)
        : _exitcode(exitcode), _result(result) {}

public:
    int _exitcode; // 计算结果是否可靠
    int _result;   // 计算结果
};
class Request
{
public:
    Request(int start, int end, const string &threadname)
        : _start(start), _end(end), _threadname(threadname) {}

    static void *SumCount(void *args)
    {
        Request *rq = static_cast<Request *>(args);
        Response *rsp = new Response(0, 0);
        for (int i = rq->_start; i <= rq->_end; i++)
        {
            rsp->_result += i;
        }
        return (void *)rsp;
    }

public:
    int _start;
    int _end;
    string _threadname;
};

int main()
{
    pthread_t tid;
    Request *rq = new Request(1, 100, "线程 1"); // 修正构造函数参数
    void *ret = nullptr;

    // 创建线程
    if (pthread_create(&tid, nullptr, Request::SumCount, rq) != 0)
    {
        cerr << "Error creating thread" << endl;
        delete rq; // 如果线程创建失败,释放 rq
        return 1;
    }

    // 等待线程结束
    if (pthread_join(tid, &ret) != 0)
    {
        cerr << "Error joining thread" << endl;
    }
    else
    {
        Response *rsp = static_cast<Response *>(ret);
        cout << "Result: " << rsp->_result << " " << "Exitcode: " << rsp->_exitcode << endl;
        delete rsp; // 释放 Response 对象
    }

    delete rq; // 释放 Request 对象
    return 0;
}

 

这样就符合面向对象的特性,和以前比起来就优雅太多了!!! 

获取线程id的函数接口和线程分离接口比较简单就一起介绍了

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* thread_run(void* arg)
{
	pthread_detach(pthread_self());
	printf("%s\n", (char*)arg);
	return NULL;
}
int main(void)
{
	pthread_t tid;
	if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0) {
		printf("create thread error\n");
		return 1;
	}
	int ret = 0;
	sleep(1);//很重要,要让线程先分离,再等待
	if (pthread_join(tid, NULL) == 0) {
		printf("pthread wait success\n");
		ret = 0;
	}
	else {
		printf("pthread wait failed\n");
		ret = 1;
	}
	return ret;
}

这段代码是单独说明线程分离。和前面的代码不同。C写的

string toHex(pthread_t tid)
{
    char hex[64];
    snprintf(hex, sizeof(hex), "%p", tid);
    return hex;
}

//获取的ID是十进制的,我们转换成16进制。所以写了这个函数

打出的来的ID怎么和我们预想不一样?不应该是LWP吗? 这里就需要我们重新理解线程了。

4.重新理解线程

画了一个简单的图,这里的用户指的是程序员。由于Linux是没有线程的概念,所以说内核中OS根本就不知道线程的存在。所谓的LWP指的是轻量级进程ID,而不是线程的ID,而这个ID是OS方便管理轻量级进程,是给OS用的,而不是给用户用的。

由于我们用户是需要线程的概念,那这个所谓的线程又是谁给用户提供的?

很简单,由于我们使用的是原生线程库,这里简单解释一下什么是原生,线程库在我们安装Linux系统时,它就存在了。就如同你出生时,你的五官也是同时就存在了。所以线程库给我提供了线程的概念,而刚才打出来的tid像什么?像个地址。而且这个地址还是个高地址,那就说明了这个tid是地址空间中堆栈之间的地址,那不就是共享区吗?而线程库也是要加载内存中的。线程库又不是只有一个进程要用,其他进程也要用。这不就对上了?

那既然是线程库提供的,线程的那一套结构也是线程库维护的吗?

当然也是线程维护,为什么这么说?还是先看两个图

 这个函数看参数就很恶心,对用户使用成本太大,轻量级进程就是用的这个创建的,线程库帮我们进行了封装。变成pthread_create这些接口。

 

 pthread_create 对clone进行封装,所以每个线程,都会有个栈,而且这个栈还是独立的。而刚才讲的tid是个地址,其实是线程的起始地址,这样我们拿到了tib就能快速找到线程。

为什么线程的栈要是独立的?

线程局部存储:这是一种特殊的存储机制,允许每个线程拥有自己的变量副本,这些副本对于该线程是私有的,但对于其他线程则是不可见的。这不同于普通的局部变量,因为TLS变量的生命周期不仅限于函数调用的持续时间,而是与线程的生命周期相同。 

由于线程有了独立栈的存在,所以栈就能存储线程执行过程中的局部变量、函数调用的参数、返回地址等信息。

关于线程控制,我们学习了5个接口函数,如何创造一个线程,为什么要等待一个线程,如何终止一个线程,线程分离之后的变化,以及获取线程的tid的函数接口。 最后通过线程的tid重新认识了线程的概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1823291.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AutoCAD 2025 ObjectARX(C++)二次开发环境搭建

&#xff08;原文&#xff1a;https://blog.iyatt.com/?p16480&#xff09; 基本环境 AutoCAD 机械版 2025 Visual Studio 2022&#xff08;需要安装“C 桌面开发”&#xff09; 开发环境 下载 &#xff08;1&#xff09;ObjectARX SDK 下载&#xff08;提供开发使用的 …

MacOS下如何使用Tomcat

提示&#xff1a;宝子们&#xff0c;希望文章对你们有所帮助&#xff0c; 请一键三连支持博主下吧&#xff5e; 文章目录 前言一、Tomcat 压缩版二、Tomcat可执行文件版本最后 前言 电脑环境&#xff1a; MacOS Monterey 版本 12.6.8 Apple M1 Tomcat 9.0.83 Java 1.8.0_171 …

(三十)Flask之wtforms库【剖析源码上篇】

每篇前言&#xff1a; &#x1f3c6;&#x1f3c6;作者介绍&#xff1a;【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者 &#x1f525;&#x1f525;本文已收录于Flask框架从入门到实战专栏&#xff1a;《Flask框架从入…

收银系统源码-千呼新零售2.0【连锁店财务管理】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货等连锁店使用。 详细介绍请查看下…

金融与大模型:引领行业未来的创新融合

前言 在数字化浪潮席卷全球的今天&#xff0c;金融与大模型的结合正成为行业发展的新引擎。这种融合不仅为金融机构带来了前所未有的效率和准确性&#xff0c;也为金融市场的稳定与发展注入了新的活力。本文将基于当前的市场现状&#xff0c;结合金融环境的发展&#xff0c;深…

探索大数据在信用评估中的独特价值

随着我国的信用体系越来越完善&#xff0c;信用将影响越来越多的人。现在新兴的大数据信用和传统信用&#xff0c;形成了互补的优势&#xff0c;大数据信用变得越来越重要&#xff0c;那大数据信用风险检测的重要性主要体现在什么地方呢?本文将详细为大家介绍一下&#xff0c;…

【ETABS】Main phrases of ETABS .e2k file and parameter roughly study

文章目录 $ STORIES - IN SEQUENCE FROM TOP$ GRIDS$ MATERIAL PROPERTIESTYPE "Steel"TYPE "Concrete" $ REBAR DEFINITIONS$ FRAME SECTIONS$ CONCRETE SECTIONS$ SLAB PROPERTIES$ WALL PROPERTIES$ POINT COORDINATES$ LINE CONNECTIVITIES$ AREA CONN…

【Python/Pytorch - 网络模型】-- 手把手搭建E3D LSTM网络

文章目录 文章目录 00 写在前面01 基于Pytorch版本的E3D LSTM代码02 论文下载 00 写在前面 测试代码&#xff0c;比较重要&#xff0c;它可以大概判断tensor维度在网络传播过程中&#xff0c;各个维度的变化情况&#xff0c;方便改成适合自己的数据集。 需要github上的数据集…

Vue37-非单文件组件

一、组件的两种编写形式&#xff1a; 非单文件组件&#xff1b;单文件组件。 二、创建一个组件 2-1、组件中的el 组件中不写el&#xff0c;不说为谁服务。 2-2、组件中的data 因为对象形式&#xff0c;多处复用的话&#xff0c;有引用关系&#xff0c;改一处&#xff0c;另一…

6月14日 Qtday2

#include "widget.h" #include "ui_widget.h" #include <QTimer> using namespace std; Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), lab1(new QLabel(this)) //初始化一个标签显示登录状态 {//设置华清远见的标签图…

基于Django、Bootstrap的电影推荐系统,算法基于用户的协同过滤算法,有爬虫有可视化后台

背景 基于Django和Bootstrap的电影推荐系统结合了用户协同过滤算法&#xff0c;通过爬虫技术获取电影数据&#xff0c;并在可视化后台展示推荐结果。该系统旨在提供个性化的电影推荐服务&#xff0c;帮助用户发现符合其喜好的电影。 用户协同过滤算法是一种常用的推荐算法&am…

JavaSE---类和对象(上)

1. 面向对象的初步认知 1.1 什么是面向对象 Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。 面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。用面向对象…

linux的UDP广播测试:C语言代码

测试代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h>#…

信息系统架构风格-系统架构师(十)

1、信息系统架构风格是描述特定应用领域中系统组织方式的惯用模式。架构风格定义了一个系统家族&#xff0c;即一个架构定义&#xff08;&#xff09;。 A一组设计原则 B一组模式 C一个词汇表和一组约束 D一组最佳实践 解析&#xff1a; 信息系统架构风格是描述某一特定 应…

014基于SSM+Jsp的网络视频播放器

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

【教学类-36-08】20240612动物面具(通义万相)-A4大小2图扇子

背景需求&#xff1a; 【教学类-36-07】20240608动物面具&#xff08;通义万相&#xff09;-A4大小7图&15手工纸1图-CSDN博客文章浏览阅读1.1k次&#xff0c;点赞45次&#xff0c;收藏27次。【教学类-36-07】20240608动物面具&#xff08;通义万相&#xff09;-A4大小7图&…

【TB作品】MSP430 G2553 单片机 口袋板 日历 时钟 闹钟 万年历 电子时钟 秒表显示

文章目录 功能介绍操作方法部分流程图代码录制了一个演示视频可以下载观看 功能介绍 时间与日期显示&#xff1a; 实时显示当前时间&#xff08;小时、分钟、秒&#xff09;和日期&#xff08;年、月、日&#xff09;。 闹钟功能&#xff1a; 设置闹钟时间&#xff08;小时、分…

全面解说Facebook代投菲律宾真金游戏pwa广告全流程

全面解说Facebook代投菲律宾真金游戏pwa广告全流程 随着数字营销的不断发展&#xff0c;社交媒体平台如Facebook已成为广告主们争相投放的热门渠道。对于希望拓展菲律宾市场的真金游戏企业来说&#xff0c;了解并掌握在Facebook上投放广告的具体流程显得尤为重要。本文将详细介…

每天五分钟深度学习:逻辑回归算法完成m个样本的梯度下降

本文重点 上节课程我们学习了单样本逻辑回归算法的梯度下降,实际使用中我们肯定是m个样本的梯度下降,那么m个样本的如何完成梯度下降呢? m个样本的损失函数定义为: 我们定义第i个样本的dw、db为: dw和db为损失J对w和b的偏导数,因为m个样本的代价函数J是1到m个样本总损失…

Office 2021 mac/win版:智慧升级,办公新风尚

Office 2021是微软公司推出的一款高效、智能且功能丰富的办公软件套件。它集成了Word、Excel、PowerPoint等多个经典应用程序&#xff0c;旨在为用户提供更出色的办公体验。 Office 2021 mac/win版获取 Office 2021在继承了前代版本优点的基础上&#xff0c;进行了大量的优化…