Linux系统基础-多线程超详细讲解(2)_线程控制

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

Linux系统基础-多线程超详细讲解(2)_线程控制

收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1. POSIX线程库

2. 创建线程

3. 线程ID及进程地址空间布局

4. 线程终止

return 函数

pthread_exit 函数

pthread_cancel 函数 

5. 线程等待

为什么要等待线程?

函数 pthread_join

6. 分离线程


1. POSIX线程库

1. 与线程有关的函数构成了一个完整的系列, 绝大多数函数的名字都是以 "pthread_" 打头的

2. 要使用这些函数库, 要通过引入头文件 <pthread.h>

3. 链接这些线程函数库时要使用编译器命令的 "-lpthread" 选项

比如我们写makefile时, 需要添加 -lptheard 选项 

test_thread:testThread.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f test_thread

在我们编写程序时, 我们同样需要加上线程库的头文件, 后面会有解释; 

2. 创建线程

功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

 

错误检查 :  

1. 传统的一些函数, 成功返回0, 失败返回-1, 并且对全局变量errno赋值以指示错误.

2. pthreads 函数出错时不会设置全局变量errno (而大部分其他 POSIX 函数会这样做). 而是将错误代码通过返回值返回

3. pthreads 同样也提供了线程内的 errno 变量, 以支持其他使用 errno 的代码, 对于 pthreads 函数的错误, 建议通过返回值判定, 因为读取返回值要比读取线程内的 errno 变量的开销更小

比如我们下面的代码 : 

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>

void* rout(void *arg)
{
    int i;
    for(; ; )
    {
        printf("I am thread 1\n");
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    int ret;
    if((ret = pthread_create(&tid, NULL, rout, NULL)) != 0)
    {
        fprintf(stderr, "pthread_create : %s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }

    int i;
    for(; ; )
    {
        printf("I am main thread\n");
        sleep(1);
    }
}

补充 : pthread_t

pthread_t  : 用于唯一标识一个线程, 当你创建一个新线程时, 系统会生成一个 pthread_t 类型的值, 用于该线程.

代码分析 :

主函数 : 

定义了一个线程变量 pthread_t tid 来存储新创建的线程的 ID

调用了 pthread_create 创建一个新线程, 并执行 rout 函数

检查 pthread_create 的返回值已确认线程是否创建成功, 如果失败, 打印错误信息并退出

线程函数 rout :

函数定义为 void* rout (void* arg), 接收一个 void* 类型的参数或者为空

使用无限循环打印日志

输出结果: 

3. 线程ID及进程地址空间布局

1. pthread_create函数会产生一个线程 ID, 存放在第一个参数指向的地址中, 该线程 ID 和前面说的线程 ID 不是一回事.

2. 前面所说的线程 ID 属于进程调度的范畴, 因为线程是轻量级进程, 是操作系统调度器的最小单位, 所以需要一个数值来唯一表示该线程.

3. pthread_create函数第一个参数指向一个虚拟内存单元, 该内存单元的地址即为新创建线程的 ID, 属于NPTL 线程库的范畴, 线程库的后续操作, 就是根据该线程 ID 来操作线程的

4. 线程库 NPTL 提供了pthread_self函数, 可以获得自身的 ID:

 示例代码 : 

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>

void *gettid(void *arg)
{
    //tid1,2,3,4 线程
    // 将 arg 转换为字符串并打印
    const char *thread_name = (const char *)arg;
    while (true)
    {
        printf("%s: Thread ID : %lx\n", thread_name, (unsigned long)pthread_self());
        sleep(1);
    }
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;

    pthread_create(&tid1, NULL, gettid, (void *)"Thread 1");
    pthread_create(&tid2, NULL, gettid, (void *)"Thread 2");
    pthread_create(&tid3, NULL, gettid, (void *)"Thread 3");
    pthread_create(&tid4, NULL, gettid, (void *)"Thread 4");

    // 主线程 ID
    while (true)
    {
        printf("Main thread ID : %lx\n", (unsigned long)pthread_self());
        sleep(1);
    }
    return 0;
}

 结果展示 : 

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

 

4. 线程终止

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

1. 从线程函数 return, 这种方法对主线程不适用, 从 main 函数 return 相当于调用 exit

2. 线程可以调用 pthread_exit 终止自己

3. 一个线程可以调用 pthread_cancel 终止同一进程的另一个线程

return 函数

实例代码 :

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>

void *gettid(void *arg)
{
    //tid1,2,3,4 线程
    // 将 arg 转换为字符串并打印
    const char *thread_name = (const char *)arg;
    while (true)
    {
        printf("%s: Thread ID : %lx\n", thread_name, (unsigned long)pthread_self());
        if(thread_name == "Thread 1") return 0;
        else if(thread_name == "Thread 2") return 0;
        else if(thread_name == "Thread 3") return 0;
        else return 0;
        sleep(1);
    }
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;

    pthread_create(&tid1, NULL, gettid, (void *)"Thread 1");
    pthread_create(&tid2, NULL, gettid, (void *)"Thread 2");
    pthread_create(&tid3, NULL, gettid, (void *)"Thread 3");
    pthread_create(&tid4, NULL, gettid, (void *)"Thread 4");

    // 主线程 ID
    while (true)
    {
        printf("Main thread ID : %lx\n", (unsigned long)pthread_self());
        sleep(1);
    }
    return 0;
}

 

pthread_exit 函数

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

需要注意, pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局变量的或者是用 malloc 分配的, 不能在线程函数栈上分配, 因为当其他线程得到这个返回指针时线程函数已经退出了~~ 

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>

void *gettid(void *arg)
{
    //tid1,2,3,4 线程
    // 将 arg 转换为字符串并打印
    const char *thread_name = (const char *)arg;
    while (true)
    {
        printf("%s: Thread ID : %lx\n", thread_name, (unsigned long)pthread_self());
        if(thread_name == "Thread 1") pthread_exit(NULL);
        else if(thread_name == "Thread 2") pthread_exit(NULL);
        else if(thread_name == "Thread 3") pthread_exit(NULL);
        else pthread_exit(NULL);
        sleep(1);
    }
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;

    pthread_create(&tid1, NULL, gettid, (void *)"Thread 1");
    pthread_create(&tid2, NULL, gettid, (void *)"Thread 2");
    pthread_create(&tid3, NULL, gettid, (void *)"Thread 3");
    pthread_create(&tid4, NULL, gettid, (void *)"Thread 4");

    // 主线程 ID
    while (true)
    {
        printf("Main thread ID : %lx\n", (unsigned long)pthread_self());
        sleep(1);
    }
    return 0;
}

pthread_cancel 函数 

功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>

void *gettid(void *arg)
{
    //tid1,2,3,4 线程
    // 将 arg 转换为字符串并打印
    const char *thread_name = (const char *)arg;
    while (true)
    {
        printf("%s: Thread ID : %lx\n", thread_name, (unsigned long)pthread_self());
        // if(thread_name == "Thread 1") pthread_exit(NULL);
        // else if(thread_name == "Thread 2") pthread_exit(NULL);
        // else if(thread_name == "Thread 3") pthread_exit(NULL);
        // else pthread_exit(NULL);
        sleep(1);
    }
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;

    pthread_create(&tid1, NULL, gettid, (void *)"Thread 1");
    pthread_create(&tid2, NULL, gettid, (void *)"Thread 2");
    pthread_create(&tid3, NULL, gettid, (void *)"Thread 3");
    pthread_create(&tid4, NULL, gettid, (void *)"Thread 4");

    sleep(5);
    pthread_cancel(tid1);
    pthread_cancel(tid2);
    pthread_cancel(tid3);
    pthread_cancel(tid4);


    // 主线程 ID
    while (true)
    {
        printf("Main thread ID : %lx\n", (unsigned long)pthread_self());
        sleep(1);
    }
    return 0;
}

 

5. 线程等待

为什么要等待线程?

1. 已经退出的线程, 其空间没有被释放, 仍然在进程的地址空间内.

2. 创建新的线程不会服用刚才退出的线程的地址空间

函数 pthread_join

功能:等待线程结束
原型
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 参数

 

实例代码  :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>

void *thread1(void *arg)
{
    printf("thread 1 returning ...\n");
    int *p = (int*)malloc(sizeof(int));
    *p = 1;
    return (void *)p;
}

void *thread2(void *arg)
{
    printf("thread 2 returning ...\n");
    int *p = (int*)malloc(sizeof(int));
    *p = 2;
    pthread_exit((void *)p);
}

void *thread3(void *arg)
{
    while(1)
    {
        printf("thread 3 is running ...\n");
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t tid;
    void *ret;

    //thread 1 return 
    pthread_create(&tid, NULL, thread1, NULL);
    pthread_join(tid, &ret);
    printf("thread return , thread id %X, return code:%d\n", tid, *(int*)ret);
    free(ret);

    //thread 2 exit
    pthread_create(&tid, NULL, thread2, NULL);
    pthread_join(tid, &ret);
    printf("thread return , thread id %X , return code : %d\n", tid, *(int*)ret);
    free(ret);

    // thread 3 cancel by other
    pthread_create(&tid, NULL, thread3, NULL);
    sleep(3);
    pthread_cancel(tid);
    pthread_join(tid, &ret);
    if(ret == PTHREAD_CANCELED)
        printf("thread return , thread id %X, return code : PTHREAD_CANCELED\n", tid);
    else
        printf("thread return , thread id %X , return code : NULL\n", tid);
}

 

线程函数定义:

thread1 : 

动态分配一个整数, 返回指向该整数的指针

thread2:

动态分配一个整数, 使用 pthread_exit 返回指针

thread3:

无限循环打印输出消息, 表示线程正在运行

主函数逻辑:

创建等待 thread1:
使用 thread_create创建线程 thread1, 使用 thread_join 等待该线程结束, 并获取返回值, 输出线程 ID 和返回的整数数值, 最后释放动态分配的内存

创建并等待 thread2

使用 pthread_create 创建线程 thread2, 同样使用 pthread_join 等待线程结束并获取返回值, 输出线程 ID 和返回值, 释放动态内存

创建 thread3 并取消:

创建 thread3 , 主线程休眠3秒, 然后调用 pthread_cancel 来取消 thread3, 使用 pthread_join等待 thread3 结束, 并获取返回值, 检查返回值, 如果返回值是 PTHREAD_CANCELED, 则表示线程被取消, 输出相关信息~

6. 分离线程

1. 默认情况下, 新创建的线程是 joinable 的, 线程退出后, 需要对其进行 pthread_join 操作, 否则无法释放资源, 从而造成系统泄露.

2. 如果不关心线程的返回值, join 是一种负担, 这个时候, 我们可以告诉系统, 当线程退出时, 自动释放线程资源. 

int pthread_detach(pthread_t thread);

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

pthread_detach(pthread_self());

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

实例代码 : 

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>

void *thread_run(void *arg)
{
    pthread_detach(pthread_self());
    printf("%s\n", (char*)arg);
    return NULL;
}

int main()
{
    pthread_t tid;
    if(pthread_create(&tid, NULL, thread_run, (void*)"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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

「C/C++」C/C++ 之 判断语句

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

2024年10月HarmonyOS应用开发者基础认证全新题库

注意事项&#xff1a;切记在考试之外的设备上打开题库进行搜索&#xff0c;防止切屏三次考试自动结束&#xff0c;题目是乱序&#xff0c;每次考试&#xff0c;选项的顺序都不同 这是基础认证题库&#xff0c;不是高级认证题库注意看清楚标题 高级认证题库地址&#xff1a;20…

go语言进阶之并发基础

并发 什么是并发&#xff0c;也就是我们常说的多线程&#xff0c;多个程序同时执行。 并发的基础 线程和进程 进程 进程是操作系统中一个重要的概念&#xff0c;指的是一个正在运行的程序的实例。它包含程序代码、当前活动的状态、变量、程序计数器和内存等资源。进程是系…

迷你航拍高清智能无人机技术详解

迷你航拍高清智能无人机技术是一种结合了高清影像拍摄、智能控制、稳定悬停以及便携性等特点的无人机技术。以下是对该技术的详细解析&#xff1a; 一、技术特点 1. 高清影像拍摄&#xff1a; 高分辨率传感器&#xff1a;迷你航拍无人机通常搭载高分辨率的相机传感器&#xf…

macOS Sonoma 14.7.1 (23H222) Boot ISO 原版可引导镜像下载

macOS Sonoma 14.7.1 (23H222) Boot ISO 原版可引导镜像下载 2024 年 10 月 28 日&#xff0c;Apple 智能今日登陆 iPhone、iPad 和 Mac。用户现可借助 Apple 智能优化写作&#xff0c;为通知、邮件和消息生成摘要&#xff0c;体验交互更自然、功能更丰富的 Siri&#xff0c;使…

QT交互界面:实现按钮运行脚本程序

一.所需运行的脚本 本篇采用上一篇文章的脚本为运行对象&#xff0c;实现按钮运行脚本 上一篇文章&#xff1a;从0到1&#xff1a;QT项目在Linux下生成可以双击运用的程序&#xff08;采用脚本&#xff09;-CSDN博客 二.调用脚本的代码 widget.cpp中添加以下代码 #include &…

玄机-流量特征分析-常见攻击事件 tomcat

简介 在web服务器上发现的可疑活动,流量分析会显示很多请求,这表明存在恶意的扫描行为,通过分析扫描的行为后提交攻击者IP flag格式&#xff1a;flag{ip}&#xff0c;如&#xff1a;flag{127.0.0.1} 找到攻击者IP后请通过技术手段确定其所在地址 flag格式: flag{城市英文小写…

一篇文章入门傅里叶变换

文章目录 傅里叶变换欧拉公式傅里叶变换绕圈记录法质心记录法傅里叶变换公式第一步&#xff1a;旋转的表示第二步&#xff1a;缠绕的表示第三步&#xff1a;质心的表示最终步&#xff1a;整理积分限和系数 参考文献 傅里叶变换 在学习傅里叶变换之前&#xff0c;我们先来了解一…

基于vue框架的的汇生活家居商城的设计与实现bdjlq(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;商品分类,商品信息,用户 开题报告内容 开题报告 项目名称&#xff1a;基于Vue框架的汇生活家居商城的设计与实现 一、项目背景与意义 随着互联网技术的不断发展和普及&#xff0c;电子商务已成为现代商业的重要组成部分。家居商城作…

《高频电子线路》 —— 高频小信号放大器的分类和质量指标

文章内容来源于【中国大学MOOC 华中科技大学通信&#xff08;高频&#xff09;电子线路精品公开课】&#xff0c;此篇文章仅作为笔记分享。 高频小信号放大器的分类和质量指标 分类 质量指标 增益 通频带 可以表示为一般情况下的电压放大倍数&#xff0c;除以谐振时候的电压放…

江协科技STM32学习- P24 DMA数据转运DMA+AD多通道

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

Unity Newtonsoft.Json 大对象序列化失败

Unity Newtonsoft.Json 大对象序列化失败 &#x1f4a3;崩溃了没&#xff1f;&#x1f600;替代方案 &#x1f4a3;崩溃了没&#xff1f; Newtonsoft.Json.JsonTextWriter:WriteValueInternal(string,Newtonsoft.Json.JsonToken) InvalidCastException: Specified cast is not…

Xcode 16.1 (16B40) 发布下载 - Apple 平台 IDE

Xcode 16.1 (16B40) 发布下载 - Apple 平台 IDE IDE for iOS/iPadOS/macOS/watchOS/tvOS/visonOS 发布日期&#xff1a;2024 年 10 月 28 日 Xcode 16.1 包含适用于 iOS 18.1、iPadOS 18.1、Apple tvOS 18.1、watchOS 11.1、macOS Sequoia 15.1 和 visionOS 2.1 的 SDK。Xco…

噩梦开始 -- 力扣83

噩梦开始了 描述&#xff1a; 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例&#xff1a; 何解&#xff1f; 1、暴力枚举&#xff1a; 遍历一遍&#xff0c;用双指针遍历&#xff0c;一个数序…

分布式搜索引擎elasticsearch操作文档操作介绍

1.DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1.DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;Domain Specific Language&#xff09;来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有数据&#xff0c;…

浏览器HTTP缓存解读(HTTP Status:200 304)

为什么要有浏览器缓存&#xff1f; 浏览器缓存(Brower Caching)是浏览器对之前请求过的文件进行缓存&#xff0c;以便下一次访问时重复使用&#xff0c;节省带宽&#xff0c;提高访问速度&#xff0c;降低服务器压力 http缓存机制主要在http响应头中设定&#xff0c;响应头中…

双十一宠物空气净化器决胜局,希喂、安德迈哪款性价比更高?

秋天到了&#xff0c;新一轮的猫咪换毛季又来了。尽管每天下班很累&#xff0c;但也不得不花上不少时间清理。有时候想偷懒&#xff0c;但身体是第一个反对的。要知道&#xff0c;长期堆积的猫毛除了会破坏家中的干净整洁外&#xff0c;浮毛还会随呼吸进入我们体内&#xff0c;…

SpringBoot--入门、创建一个SpringBoot项目、测试

一、IDEA配置maven &#xff08;1&#xff09;下载maven maven下载地址&#xff1a;Maven – Download Apache Maven &#xff08;2&#xff09;解压 解压下载好的文件&#xff1a; 创建一个文件夹maven-repository用来充当本地仓库&#xff1a; &#xff08;3&#xff09;配…

基于uniapp微信小程序的旅游系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

C++设计模式创建型模式———生成器模式

文章目录 一、引言二、生成器/建造者模式三、总结 一、引言 上一篇文章我们介绍了工厂模式&#xff0c;工厂模式的主要特点是生成对象。当对象较简单时&#xff0c;可以使用简单工厂模式或工厂模式&#xff1b;而当对象相对复杂时&#xff0c;则可以选择使用抽象工厂模式。 工…