linux——多线程,线程控制

news2024/10/5 17:19:51

目录

一.POSIX线程库

二.线程创建

1.创建线程接口

2.查看线程

3.多线程的健壮性问题

4.线程函数参数传递

5.线程id和地址空间

三.线程终止

1.pthread_exit

2.pthread_cancel

四.线程等待 

五.线程分离


一.POSIX线程库

站在内核的角度,OS只有轻量级进程,没有线程的概念,但是站在用户的角度我们只有线程没有轻量级进程的概念。因为Linux下没有真正意义上的线程,而是用进程模拟的线程,所以Linux不会提供直接创建线程的系统调用,最多给我们提供创建轻量级进程的接口。

所以linux对下对LWP的接口进行封装,对上给用户提供线程控制的接口——POSIX线程库,pthread库,这是任何一个linux系统都会带的库,又叫原生线程库。

POSIX线程:

  1. 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的。
  2. 要使用这些函数库,要通过引入头文<pthread.h>。
  3. 链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

 二.线程创建

1.创建线程接口

功能:创建一个新的线程。
原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
void *(*start_routine)(void*), void *arg);

参数:

  1. thread:返回线程ID。
  2. attr:设置线程的属性,attr为NULL表示使用默认属性。
  3. start_routine:是个函数地址,线程启动后要执行的函数,该函数返回值是void*,参数是void*。
  4. arg:传给线程启动函数的参数。

返回值:

  • 成功返回0;失败返回错误码。

 测试代码:

#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void *FuncRun(void *argc)
{
    while (1)
    {
        cout << "I am thread,my pid:" << getpid() << endl;
        sleep(1);
    }
}

int main()
{
    //线程id
    pthread_t id;
    //创建线程
    pthread_create(&id, NULL, FuncRun, NULL);
    while (1)
    {
        cout << "I am main,my pid:" << getpid() << endl;
        sleep(1);
    }
    return 0;
}

测试结果:

说明:

  1. 线程没有父子之分,但是线程有主线程,和新线程的区分。
  2. 我们可以看到,主线程pid和新线程的pid是相同的。因为他们本身就是同一个进程的一部分。
  3. 新线程和主线程谁先被调度取决于调度器。

 2.查看线程

查看线程使用命令:

ps -aL

 3.多线程的健壮性问题

 测试代码:

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

using namespace std;

void *FuncRun1(void *argc)
{
    int count = 0;
    while (1)
    {
        count++;
        cout << "I am thread1,my pid:" << getpid() << endl;
        if (count == 5)
        {
            int tmp = count / 0;
        }
        sleep(1);
    }
}
void *FuncRun2(void *argc)
{
    while (1)
    {
        cout << "I am thread2,my pid:" << getpid() << endl;
        sleep(1);
    }
}

int main()
{
    // 线程id
    pthread_t id1, id2;
    // 创建线程
    pthread_create(&id1, NULL, FuncRun1, NULL);
    pthread_create(&id2, NULL, FuncRun2, NULL);

    while (1)
    {
        cout << "I am main,my pid:" << getpid() << endl;
        sleep(1);
    }

    return 0;
}

测试结果:

说明:

  1. 代码其中有一个线程在第五秒的时候,会出现一个除0的问题。
  2. 当一个线程因为某一个错处而导致线程终止的时候,整个进程也都会直接终止。
  3. 因为信号是发送给进程的,最终OS直接对由信号做出的处理动作也是针对进程的。

 4.线程函数参数传递

 我们想通过给线程函数传参让线程执行更加复杂的任务。

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

using namespace std;

// 任务,计算[1-top]的求和,并将结果存储到sum中。
struct task
{
    task(int top, int num)
        : _thread_name("thread" + to_string(num)), _top(top), _sum(0), _num(num)
    {
    }

    string _thread_name; // 线程名字
    int _top;            // 计算数据范围
    int _sum;            // 结果
    int _num;            // 线程编号
};

// 线程函数
void *FuncRun(void *argc)
{
    task *t = (task *)argc;
    for (int i = 1; i <= t->_top; i++)
    {
        t->_sum += i;
    }
}

int main()
{
    // 线程id
    pthread_t id1, id2;
    // 创建线程

    task t1(100, 1);
    task t2(150, 2);

    pthread_create(&id1, NULL, FuncRun, &t1);
    pthread_create(&id2, NULL, FuncRun, &t2);
    // 等待线程计算完再输出结果
    sleep(1);

    cout << t1._thread_name << ":[1-" << t1._top << "]=" << t1._sum << endl;
    cout << t2._thread_name << ":[1-" << t2._top << "]=" << t2._sum << endl;

    return 0;
}

测试结果:

说明:

  • 由于接口的设计上参数的类型是void* ,这也就使得我们可以给线程函数传递的参数是非常丰富的。

5.线程id和地址空间

  1. pthread_ create 函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
  2. 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
  3. pthread_ create 函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

我们在对线程做操作的时候,根本上是使用线程库对线程进行操作,那么线程库本质就是存在于linux上的动态库,在我们使用线程库的时候,线程库也会向普通的动态库一样加载到共享区中,我们使用线程库方法就是访问自己的地址空间。

线程库中需要被管理的线程会有很多,线程库也必然实现了线程的数据结构——TCB(线程控制块)。

每个线程都有自己的TCB,和独立的上下文数据,以及线程栈空间。pthread_t 就是指向他们的首地址的一个地址。

 三.线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

1.pthread_exit

功能:线程终止.
原型:void pthread_exit(void *value_ptr);
参数:value_ptr:value_ptr不要指向一个局部变量,返回线程结果。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)。

 测试代码:

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

using namespace std;

void *FuncRun1(void *argc)
{
    int count = 0;
    while (1)
    {
        count++;
        cout << "I am thread-1-count:" << count << endl;
        sleep(1);
        // 三秒后线程1退出
        if (count == 3)
        {
            pthread_exit(NULL);
        }
    }
}

void *FuncRun2(void *argc)
{
    int count = 0;
    while (1)
    {
        count++;
        cout << "I am thread-2-count:" << count << endl;
        sleep(1);
        // 三秒后线程2退出
        if (count == 3)
        {
            pthread_exit(NULL);
        }
    }
}

int main()
{
    // 线程id
    pthread_t id1, id2;
    // 创建线程
    pthread_create(&id1, NULL, FuncRun1, NULL);
    pthread_create(&id2, NULL, FuncRun2, NULL);

    while (1)
    {
        cout << "I am main,my pid:" << getpid() << endl;
        sleep(1);
    }

    return 0;
}

测试结果:

说明:

  1. 线程在退出后,主线程并没有受到影响,进程也有没受到影响。
  2. 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

2.pthread_cancel

功能:取消一个执行中的线程
原型:int pthread_cancel(pthread_t thread);
参数:thread:线程ID
返回值:成功返回0;失败返回错误码

 测试代码:


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

using namespace std;

void *FuncRun1(void *argc)
{
    int count = 0;
    while (1)
    {
        count++;
        cout << "I am thread-1-count:" << count << endl;
        sleep(1);
    }
}
void *FuncRun2(void *argc)
{
    int count = 0;
    while (1)
    {
        count++;
        cout << "I am thread-2-count:" << count << endl;
        sleep(1);
    }
}
int main()
{
    // 线程id
    pthread_t id1, id2;
    // 创建线程
    pthread_create(&id1, NULL, FuncRun1, NULL);
    pthread_create(&id2, NULL, FuncRun2, NULL);
    int count = 0;
    while (1)
    {
        count++;
        sleep(1);

        if (count == 3)
        {
            // 三秒后终止线程
            pthread_cancel(id1);
            pthread_cancel(id2);
        }
        cout << "I am main" << endl;
    }

    return 0;
}

测试结果:

四.线程等待 

为什么需要线程等待?

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。

 功能:等待线程结束。
原型:int pthread_join(pthread_t thread, void **value_ptr);
参数:thread:线程ID。
value_ptr:它指向一个指针,后者指向线程的返回值。
返回值:成功返回0;失败返回错误码。

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下: 

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数,PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

 测试代码:

void *FuncRun1(void *argc)
{
    int *top = (int *)argc;
    int *sum = new int;
    for (int i = 1; i <= *top; i++)
    {
        *sum += i;
    }
    // 线程退出
    pthread_exit(sum);
}

void *FuncRun2(void *argc)
{
    int *top = (int *)argc;
    int *sum = new int;
    for (int i = 1; i <= *top; i++)
    {
        *sum += i;
    }
    // 线程退出
    return sum;
}

void *FuncRun3(void *argc)
{
    int *top = (int *)argc;
    int *sum = new int;
    for (int i = 1; i <= *top; i++)
    {
        *sum += i;
        sleep(1);
    }
    free(sum);
    // 线程退出
}

int main()
{

    int top1 = 100;
    int top2 = 150;
    int top3 = 200;

    pthread_t id1;
    pthread_t id2;
    pthread_t id3;

    pthread_create(&id1, NULL, FuncRun1, &top1);
    pthread_create(&id2, NULL, FuncRun2, &top2);
    pthread_create(&id3, NULL, FuncRun3, &top3);
    pthread_cancel(id3);

    // 接受线程返回数据
    void *ret_ptr1;
    void *ret_ptr2;
    void *ret_ptr3;
    // 等待线程
    pthread_join(id1, &ret_ptr1);
    pthread_join(id2, &ret_ptr2);
    pthread_join(id3, &ret_ptr3);

    
    cout << "ret1:" << *((int *)ret_ptr1) << endl;
    free(ret_ptr1);
    cout << "ret2:" << *((int *)ret_ptr2) << endl;
    free(ret_ptr2);
    if (ret_ptr3 == PTHREAD_CANCELED)
        cout << "ret3:PTHREAD_CANCELED" << endl;

    return 0;
}

测试结果:

五.线程分离

  1.  默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  2. 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

pthread_detach(pthread_self());

joinable分离是冲突的,一个线程不能既是joinable又是分离的。

测试代码:


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <pthread.h>
#include <unistd.h>

using namespace std;
void *FuncRun(void *argc)
{
    // 线程分离
    pthread_detach(pthread_self());
    int count = 0;
    while (1)
    {
        count++;
        cout << "I am thread-count:" << count << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    int n = pthread_create(&tid, NULL, FuncRun, NULL);
    if (n != 0)
    {
        cerr << "pthread_create:" << strerror(errno) << endl;
    }

    sleep(2);

    // 线程已经分离,再去线程等待,pthread_join会立即报错。
    if (pthread_join(tid, NULL) == 0)
    {
        printf("pthread wait success\n");
    }
    else
    {
        printf("pthread wait failed\n");
    }

    return 0;
}

测试结果:

 

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

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

相关文章

TensorFlow入门(十六、识别模糊手写图片)

TensorFlow在图像识别方面,提供了多个开源的训练数据集,比如CIFAR-10数据集、FASHION MNIST数据集、MNIST数据集。 CIFAR-10数据集有10个种类,由6万个32x32像素的彩色图像组成,每个类有6千个图像。6万个图像包含5万个训练图像和1万个测试图像。 FASHION MNIST数据集由衣服、鞋子…

软件行业与就业(导师主讲)

在企业软件应用的整体架构体系中&#xff0c;有一部分被称为中间件&#xff0c;那么什么叫中间件&#xff1f; 中间件&#xff08;Middleware&#xff09;是指位于操作系统和应用程序之间的一层软件层&#xff0c;它提供了一组工具和服务&#xff0c;用于简化和增强企业软件应用…

Generative AI 新世界 | 文生图领域动手实践:预训练模型的微调

在上期文章&#xff0c;我们探讨了预训练模型的部署和推理&#xff0c;包括运行环境准备、角色权限配置、支持的主要推理参数、图像的压缩输出、提示工程 (Prompt Engineering)、反向提示 (Negative Prompting) 等内容。 亚马逊云科技开发者社区为开发者们提供全球的开发技术资…

掌握C语言:开启编程世界的大门

掌握C语言&#xff1a;开启编程世界的大门 C语言编写的程序通常更高效&#xff0c;代码行数更少&#xff0c;适用于需要高性能的场景。掌握C语言还为你打开了学习其他高级编程语言的大门。C语言拥有庞大的开源社区和丰富的现成代码库&#xff0c;为你快速开发算法和函数提供了…

Nginx配置ssl证书(https证书)

Nginx配置ssl证书(https证书) 安装nginxNginx 的 SSL 模块安装下载Nginx 服务证书配置nginx.conf 安装nginx 搭建服务器,安装docker-compose https://blog.csdn.net/qq_33240556/article/details/124789530 安装docker-compose nginx https://blog.csdn.net/qq_33240556/artic…

做运维有前途吗?

不管男生女生&#xff0c;都不建议做运维&#xff01;&#xff01;就一个原因&#xff0c;性价比太低&#xff01;需要会的东西多&#xff0c;没有一个统一的运维标准&#xff01;你心目中的运维和别人心目中的运维&#xff0c;不是一个运维&#xff01;也不建议做测试&#xf…

【软件测试】博客系统项目测试报告(ssm项目)

文章目录 一. 报告概要二. 引言三. 测试环境四. 测试执行概况及功能测试1. 手工测试1.1 编写测试用例1.2 执行部分测试用例 2. 自动化测试Selenium2.1 编写测试用例2.2自动化测试代码1. 自动化测试工具类2. 博客登录页测试3. 博客注册页4. 博客详情页5. 博客编辑页6. 博客列表页…

易点易动:解决纸质固定资产审批痛点,助您高效自定义审批流程

固定资产审批是企业日常管理中不可或缺的环节&#xff0c;然而&#xff0c;传统的纸质审批流程常常面临繁琐、低效的问题。易点易动作为一款先进的固定资产管理系统&#xff0c;以其自定义设置流程的特点&#xff0c;为企业打破审批瓶颈&#xff0c;实现高效审批提供了理想解决…

轻量级虚拟化技术草稿

Support Tech ST.1 virtiofs ST.1.1 fuse framework 引用wiki中关于fuse的定义&#xff1a; Filesystem in Userspace (FUSE) is a software interface for Unix and Unix-like computer operating systems that lets non-privileged users create their own file systems w…

Python 编程基础概念

目录 1 Python程序的构成1.1 使用\行连接符 2 对象3 引用4 标识符4.1 Python标识符命名规则 5 变量和简单赋值语句5.1 变量的声明和赋值5.2 删除变量和垃圾回收机制5.3 链式赋值5.4 系列解包赋值5.5 常量 6 最基本内置数据类型和运算符6.1 基本运算符6.2 整数6.3 浮点数6.4 类型…

华为数通方向HCIP-DataCom H12-831题库(多选题:241-259)

第241题 设备产生的信息可以向多个方向输出信息,为了便于各个方向信息的输出控制,信息中心定义了10条信息通道,使通道之间独立输出,缺省情况下,以下哪些通道对应的输出方向可以接收Trap信息? A、console通道 B、logbuffer通道 C、snmpagent通道 D、trapbuffer通道 答案:…

山西电力市场日前价格预测【2023-10-11】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-10-11&#xff09;山西电力市场全天平均日前电价为507.37元/MWh。其中&#xff0c;最高日前电价为873.70元/MWh&#xff0c;预计出现在18: 45。最低日前电价为313.23元/MWh&#xff0c;预计…

互联网从业者如何调节压力

互联网从业者面临着多种压力源&#xff0c;如工作性质、竞争、项目失败、对失败的恐惧等&#xff0c;这些压力会影响身心健康以及工作效率。因此&#xff0c;采取有效的压力调节方法是必要的&#xff0c;接下来我们从三个方向探讨下互联网从业者有关压力来源、应对压力的方法及…

leetcode每日一练-第977题-有序数组的平方

一、思路 双指针 二、 解题方法 i指向起始位置&#xff0c;j指向终止位置。 定义一个新数组result&#xff0c;和A数组一样的大小&#xff0c;让k指向result数组终止位置。 如果A[i] * A[i] < A[j] * A[j] 那么result[k--] A[j] * A[j]; 。 如果A[i] * A[i] > A[j…

Echarts使用感受

目录 数据处理 遇到的问题 更换echart主题 Y轴数字后添加百分比号 eCharts饼图显示百分比 echarts自定义主题的手把手教学 查看UI图 点击下方链接页面的定制主题按钮 点击下载主题 点击主题下载的JSON版本&#xff0c;点击复制 ​编辑 新建js文件&#xff0c;把复制的…

【Java】什么是API

API (Application Programming Interface,应用程序编程接口) Java中的API 指的就是 JDK 中提供的各种功能的 Java类&#xff0c;这些类将底层封装起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要学习这些类如何使用即可&#xff0c;我们可以通过帮助文档…

二、监控搭建-Prometheus-采集端部署

二、监控搭建-Prometheus-采集端部署 1、背景2、目标3、传承4、操作 1、背景 在上一篇中我们搭建了Prometheus平台&#xff0c;平台的搭建跟Linux系统上面安装了vim软件一样&#xff0c;给的只是一个很好的铸剑玄铁&#xff0c;具体的使用需要打磨和配件的运用。 2、目标 使…

XGBoost 2.0:对基于树的方法进行了重大更新

XGBoost是处理不同类型表格数据的最著名的算法&#xff0c;LightGBM 和Catboost也是为了修改他的缺陷而发布的。9月12日XGBoost发布了新的2.0版&#xff0c;本文除了介绍让XGBoost的完整历史以外&#xff0c;还将介绍新机制和更新。 这是一篇很长的文章&#xff0c;因为我们首…

转守为攻,亚马逊云换帅背后的战略转向

点击关注 文&#xff5c;刘雨琦 一则人事任命&#xff0c;揭开了亚马逊云在大中华区反击战的序幕。 10月9日&#xff0c;亚马逊云科技全球销售、市场和服务高级副总裁 Matt Garman 宣布了大中华区领导人变更任命&#xff0c;储瑞松将接替张文翊担任该职位&#xff0c;继续带领…

2023年网络安全岗位有哪些?金九银十别错过秋招!

网络安全有哪些岗位&#xff1f; 1. 安全服务工程师 7-10k 网络安全工程师、安全项目经理&#xff1a;主要负责甲方设备安全调试工作。需精通服务器、网络技术以及安全设备原理与配置。 2. 安全运维工程师 7-10k 安全运维工程师&#xff0c;主要对己方安全防御体系的运维和应急…