头歌 Linux之线程管理

第1关:创建线程

任务描述


通常我们编写的程序都是单进程,如果在一个进程中没有创建新的线程,则这个单进程程序也就是单线程程序。本关我们将介绍如何在一个进程中创建多个线程。

本关任务:学会使用C语言在Linux系统中使用pthread_create库函数创建一个新的线程。

相关知识


通常我们编写的程序都是单线程程序,单线程的程序都是按照一定的顺序按序的执行。有些情况下,我们需要在一个进程中同时执行多个控制流程,这时候线程就派上了用场。例如,我们需要实现一个在线音乐播放器,一方面我们在线播放用户选中的音乐,另一方面又需要同时下载曲子,这些任务需要同时被执行,而不是按序一个一个的执行,这样才会使得用户一边播放音乐,一边下载自己喜欢的曲子。针对以上需求,我们可以用多线程实现,一个线程专门在线播放用户选中的音乐,另外一个线程专门用户下载曲子。

通常,一个进程只包含一个线程,我们把这个线程叫做主线程,例如main函数就是一个主线程。如果在主线程里创建多个线程,那么程序就会在创建线程的地方产生分支,变成了多个程序来同时运行。这似乎和我们以前学习的多进程一样,其实背后的原理还是有所区别。

在多进程中,子进程是通过拷贝父进程的地址空间来实现,而在多线程中,同一进程中的所有线程都是共享程序代码,一段代码可以被多个线程来执行。

在Linux系统中,我们可以通过pthread_create函数来创建线程。我们可以使用man命令来查询该函数的使用方法。具体的查询命令为:man 3 pthread_create

使用pthread_create函数创建线程

pthread_create函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>

函数格式如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数说明:

thread:该参数是一个指针,当线程创建成功后,用来返回创建的线程ID;
attr:该参数用于指定线程的属性,NULL表示使用默认属性,通常我们使用默认属性;
start_routine:该参数为一个函数指针,指向线程创建后要调用的函数,也被称为线程函数;
arg:该参数指向传递给线程函数的参数;


函数返回值说明:


调用成功,pthread_create返回值为0;调用失败返回一个非零的值。

注意:pthread_create一旦调用成功,新创建的线程将开始运行第3个参数所指向的函数,原来的线程继续往下运行。由于线程是第三库所提供的,因此在编译包含线程的程序时,我们需要手动链接线程库,只需在编译命令加上-lpthread参数即可。

案例演示1:


编写一个程序,使用pthread_create函数创建一个线程,在新创建的线程中打印一个字符串,在主线程中也打印一个字符串。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    printf("This is My first thread\n");
    
    return NULL;
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    sleep(1);
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为createThread.c文件,编译执行。可以看到新创建的线程被调用成功。

案例演示2:


编写一个程序,使用pthread_create函数创建两个线程,并在每个线程中接受主线程传来的字符串,并将其打印出来。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    printf("print String: %s\n", (char *)arg);
    return NULL;
}
int main()
{
    pthread_t thread1, thread2;
    int ret = pthread_create(&thread1, NULL, printString, "This is first thread");
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    ret = pthread_create(&thread2, NULL, printString, "This is second thread");
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    sleep(1);
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为printThread.c文件,编译执行。可以看到新创建的按照主线程传递的参数,将指定的字符串打印出来。

注意:编译程序的时候需要手动加上线程库-lpthread。否则,编译时会出错。

编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:

补全createThread函数,使用pthread_create函数创建线程,并将start_routine作为线程处理函数,arg作为线程处理函数的参数,同时将创建成功的线程ID作为createThread函数的返回值。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <stdio.h>
#include <pthread.h>

/************************
 * 参数start_routine: 函数指针,用于指向线程函数
 * 参数arg: 是线程函数的参数
 * 返回值: 返回线程ID
*************************/
pthread_t createThread(void *(*start_routine) (void *), void *arg)
{
	pthread_t thread;
	/********** BEGIN **********/
	
	int ret = pthread_create(&thread, NULL, start_routine,arg);  
	/********** END **********/

	return thread;
}

第2关:线程挂起

任务描述


在学习多进程编程的时候,我们学习了如何等待一个进程结束,那么在多线程中也存在同样的操作,如何使得一个线程挂起等待其他的线程先执行。本关我们将介绍如何挂起一个线程,并等待指定线程。

本关任务:学会使用C语言在Linux系统中使用pthread_join库函数挂起当前线程,并等待指定的线程。

相关知识


通过上一管卡的学习,我们学会了如何创建一个线程。在上一关中我们遗留了一个未解决的问题,不知道细心的你发现没,那就是我们在案例演示中使用了sleep函数给主线程睡眠了1秒,如果主线程先退出,那么新创建的线程会发生什么?正确答案是,如果主线程先退出,那么还未执行完成的其他所有线程将被终止。因此,保证主线程最后一个退出是非常重要的。

接下来,我们用实例验证如果主线程先退出,那么其他的线程会不会受到影响,将上一关中的案例一修改成如下:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    sleep(1);
    printf("This is My first thread\n");
    
    return NULL;
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    printf("This is main thread\n");
    
    return 0;
}

编译执行。可以看到当主线程退出后,新创建的线程是不会继续执行的。

在Linux系统中提供了挂起当前线程,用来等待一个指定线程结束的库函数pthread_join。这个函数相当于进程等待函数waitpid,他可以挂起当前线程,并一直等待指定线程,直到指定线程退出后,该函数才会返回继续执行。

在Linux系统中,我们可以通过pthread_join函数来挂起线程。我们可以使用man命令来查询该函数的使用方法。具体的查询命令为:man 3 pthread_join

使用pthread_join挂起线程


pthread_join函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>

函数格式如下:
int pthread_join(pthread_t thread, void **retval);

参数说明:

thread:该参数是一个线程ID,用于指定要等待其终止的线程;
retval:该参数用于存放等待线程的返回值,如果不关注线程的退出值,则可以设置为NULL;


函数返回值说明:


调用成功,pthread_join返回值为0;调用失败返回一个非零的值。

注意:由于线程是第三库所提供的,因此在编译包含线程操作的程序时,我们需要手动链接线程库,只需在编译命令加上-lpthread参数即可。

案例演示1:


编写一个程序,使用pthread_join函数挂起当前线程,等待新创建的线程先执行。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    sleep(1);
    printf("This is My first thread\n");
    
    return NULL;
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    if(pthread_join(thread, NULL) != 0)
    {
        printf("等待线程失败\n");
        return -1;
    }
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为joinThread.c文件,编译执行。可以看到尽管新创建的线程睡眠了1秒,然后还是被正常的运行完。
注意:编译程序的时候需要手动加上线程库-lpthread。否则,编译时会出错。

编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:

补全waitThread函数,使用pthread_join函数挂起当前线程,等待指定线程结束,thread为要等待的线程ID号,waitThread函数等待线程成功返回0,失败返回-1。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <stdio.h>
#include <pthread.h>

/************************
 * 参数thread: 需要等待结束的线程ID号
 * 返回值: 等待成功返回0,失败返回-1
 * 提示: 忽略线程返回值
*************************/
int waitThread(pthread_t thread)
{
	int ret = -1;
	/********** BEGIN **********/
	int pthread_join(pthread_t thread, void **waitThread);
	if(pthread_join(thread, NULL) != 0)  
	
	/********** END **********/

	return ret;
}

第3关:线程终止

任务描述


在学习多进程编程的时候,我们知道进程的退出有很多中方式,常见的有exit函数,而线程的退出也有多种方法。本关我们将介绍如何终止一个线程的执行。

本关任务:学会使用C语言在Linux系统中终止一个线程。

相关知识


Linux下有三种方式可以是一个线程终止,分别是:(1)通过return从线程函数返回;(2)通过调用pthread_exit使得一个线程退出;(3)通过调用pthread_cancel终止一个线程;

有两种特殊情况要注意:第一种情况就是,在主线程中,如果从main函数返回或者是调用exit函数来终止主线程的执行,则整个进程将终止执行,此时进程中的所有线程也将被终止执行,因此,在主线程中不能过早的退出,这就是我们上一关中所介绍的为什么要使用pthread_join函数来挂起主线程的原因。另一种情况就是,如果在主线程中调用pthread_exit函数终止主线程的执行,则仅仅是主线程消亡,进程是不会被终止的,因此进程内的其他线程也不会被终止,直到所有线程执行结束,进程才会被终止。

在上一关中,我们学习了pthread_join函数等待一个线程的结束,并获取线程退出值,那么线程以不同的方法终止,通过pthread_join得到的退出值也是不同的,总结如下:

如果线程通过调用return返回,则pthread_join所得到的退出值就是线程函数的return的值;
如果线程是通过调用pthread_cancel异常终止,则pthread_join所得到的退出值是PTHREAD_CANCELED;
如果线程是通过调用pthread_exit异常终止,则pthread_join所得到的退出值就是pthread_exit函数的参数值;
我们可以使用man命令来查询这些函数的使用方法。具体的查询命令为:man 3 函数名

使用pthread_exit退出线程


pthread_exit函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>

函数格式如下:
void pthread_exit(void *retval);

参数说明:

retval:线程的返回值;


函数返回值说明:


无返回值。

注意:由于线程是第三库所提供的,因此在编译包含线程操作的程序时,我们需要手动链接线程库,只需在编译命令加上-lpthread参数即可。

案例演示1:


编写一个程序,使用pthread_exit函数退出线程,并使用pthread_join函数获取线程的退出值。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    sleep(1);
    printf("This is My first thread\n");
    pthread_exit("thread finished");
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    void *status = NULL;
    if(pthread_join(thread, &status) != 0)
    {
        printf("等待线程失败\n");
        return -1;
    }
    printf("first thread exit(%s)\n", (char *)status);
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为exitThread.c文件,编译执行。可以看到新创建的线程退出的代码为:"thread finished",并且在主线程中使用pthread_join函数成功的获取到退出代码。
注意:编译程序的时候需要手动加上线程库-lpthread。否则,编译时会出错。

使用pthread_cancel退出线程


pthread_cancel函数的具体的说明如下:
需要的头文件如下:
#include <pthread.h>
函数格式如下:
int pthread_cancel(pthread_t thread);

参数说明:

thread:需要被取消运行的线程ID;


函数返回值说明:


调用成功,返回0,调用失败,返回一个非零值。

注意:由于线程是第三库所提供的,因此在编译包含线程操作的程序时,我们需要手动链接线程库,只需在编译命令加上-lpthread参数即可。

案例演示1:


编写一个程序,使用pthread_cancel函数退出线程,并使用pthread_join函数获取线程的退出值。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    while(1)
    {
        printf("This is My first thread\n");
        sleep(1);
    }
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    sleep(2);
    ret = pthread_cancel(thread);
    if(ret != 0)
    {
        printf("cancel thread(%lu) failure\n", thread);
        return -1;
    }
    void *status = NULL;
    if(pthread_join(thread, &status) != 0)
    {
        printf("等待线程失败\n");
        return -1;
    }
    printf("first thread exit(%d)\n", (int)status);
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为cancelThread.c文件,编译执行。可以看到新创建的线程被主线程使用pthread_cancel函数强制取消执行,并且退出的代码为:-1,也就是PTHREAD_CANCELED在Linux系统中的定义为-1。
注意:编译程序的时候需要手动加上线程库-lpthread。否则,编译时会出错。

编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:

补全cancelThread函数,使用pthread_cancel函数终止指定的线程,thread为线程要被取消的线程ID号,调用成功返回0,否则返回-1。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

开始你的任务吧,祝你成功!

解答:

#include <stdio.h>
#include <pthread.h>

/************************
 * 参数thread: 需要等待结束的线程ID号
 * 返回值: 等待成功返回0,失败返回-1
 * 提示: 忽略线程返回值
*************************/
int cancelThread(pthread_t thread)
{
	int ret = -1;
	/********** BEGIN **********/
	ret = pthread_cancel(thread);
	
	/********** END **********/

	return ret;
}

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

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

相关文章

Docker的彻底删除与重新安装(ubuntu22.04)

Docker的彻底删除与重新安装&#xff08;ubuntu22.04&#xff09; 一、首先我们彻底删除Docker1、删除docker及安装时自动安装的所有包2、删除无用的相关的配置文件3、删除相关插件4、删除docker的相关配置和目录 二、重新安装1、添加 Docker 的官方 GPG 密钥&#xff1a;2、将…

深入浅出:Go语言标准库探索

深入浅出&#xff1a;Go语言标准库探索 引言 Go语言自发布以来&#xff0c;以其简洁的语法、高效的性能和强大的并发支持赢得了开发者的青睐。除了这些特性外&#xff0c;Go还拥有一个功能丰富且设计精良的标准库&#xff0c;几乎涵盖了现代应用程序开发所需的所有基本功能。…

深入浅出:Go语言中的结构体(Struct)

深入浅出&#xff1a;Go语言中的结构体&#xff08;Struct&#xff09; 引言 结构体是Go语言中一种非常重要的数据类型&#xff0c;它允许我们定义包含多个字段的自定义数据类型。通过结构体&#xff0c;我们可以更好地组织和管理复杂的数据结构&#xff0c;使得代码更加清晰…

基于长短时记忆神经网络的空气质量数据分析与预测的设计与实现

研究内容 本文旨在研究和解决气象参数和气象aqi指数实时预测准确度较低和速度较慢的问题&#xff0c;并结合所有空气质量监测站点以及气象监测站点的实际情况&#xff0c;将长短时记忆神经网络应用于气象基本指数的预测中&#xff0c;为日后的政府决策和人类出行活动提供一定的…

burp(6)暴力破解与验证码识别绕过

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

Plugin - 插件开发05_Solon中的插件实现机制

文章目录 Pre概述插件插件扩展机制&#xff08;Spi&#xff09;插件扩展机制概述插件扩展机制的优势 插件扩展机制实现步骤第一步&#xff1a;定制插件实现类示例代码&#xff1a;插件实现类 第二步&#xff1a;通过插件配置文件声明插件示例插件配置文件&#xff1a;META-INF/…

现代密码学|Rabin密码体制及其数学基础 | 椭圆曲线密码体制及其运算 | DH密钥交换及中间人攻击

文章目录 参考Rabin密码体制及其数学基础中国剩余定理二次剩余Rabin密码体制实例 椭圆曲线密码体制及其运算原理运算规则加密解密实例 DH密钥交换及中间人攻击中间人攻击 参考 现代密码学&#xff5c;Rabin密码体制及其数学基础 现代密码学&#xff5c;椭圆曲线密码体制及其运…

分布式cap

P&#xff08;分区安全&#xff09;都能保证&#xff0c;就是在C&#xff08;强一致&#xff09;和A&#xff08;性能&#xff09;之间做取舍。 &#xff08;即立马做主从同步&#xff0c;还是先返回写入结果等会再做主从同步。类似的还有&#xff0c;缓存和db之间的同步。&am…

【TCP 网络通信(发送端 + 接收端)实例 —— Python】

TCP 网络通信&#xff08;发送端 接收端&#xff09;实例 —— Python 1. 引言2. 创建 TCP 服务器&#xff08;接收端&#xff09;2.1 代码示例&#xff1a;TCP 服务器2.2 代码解释&#xff1a; 3. 创建 TCP 客户端&#xff08;发送端&#xff09;3.1 代码示例&#xff1a;TCP…

借助 CC-Link IE FB 转 Profinet 网关实现西门子 PLC 接入三菱 PLC 系统的解决策略

我们公司自动化生产线上&#xff0c;原有的控制系统采用三菱 PLC 通过 CC-Link IEFB 网络进行通信和控制。后来随着企业生产规模的扩大和对自动化系统集成度要求的提高&#xff0c;需要将部分设备与新引入的西门子 PLC 控制系统相连接&#xff0c;而西门子 PLC 使用 ProfiNet 协…

即时通信系统项目总览

聊天室服务端项目总体介绍 本项目是一个全栈的即时通信系统, 前端使用QT实现聊天客户端, 后端采⽤微服务框架设计, 由网关子服务统一接收客户端的请求, 再分发到不同的子服务上处理并将结果返回给网关, 网关再将响应转发给客户端 拆分的微服务包含&#xff1a; 网关服务器&…

Redis的五种数据类型(Set、Zset)

目录 1. Set 集合1.1 Set介绍1.2 常见命令1.2.1 SADD命令1.2.2 SMEMBERS命令1.2.3 SISMEMBER命令1.2.4 SCARD命令1.2.5 SPOP命令1.2.6 SMOVE命令1.2.7 SREM命令 1.3 集合间操作1.3.1 SINTER命令1.3.2 SINTERSTORE命令1.3.3 SUNION命令1.3.4 SUNIONSTORE命令1.3.5 SDIFF命令1.3.…

负载均衡OJ项目中遇到的问题

1、续行符问题 关于换行符 &#xff0c;代码在使用了换行符后无法编译文件&#xff0c;也没有爆出任何错误&#xff0c;更没有按照我们的代码打印出如下类似内容 &#xff1a;[ERROR][compiler.hpp][66][1732635247]编译失败,没有形成可执行程序 随机排查才发现。 代码中的 \ …

力扣面试150 环形子数组的最大和 循环数组 逆向思维

Problem: 918. 环形子数组的最大和 &#x1f468;‍&#x1f3eb; 参考题解 ⏰ 时间复杂度&#xff1a; O ( n ) O(n) O(n) &#x1f30e; 空间复杂度&#xff1a; O ( 1 ) O(1) O(1) class Solution {public int maxSubarraySumCircular(int[] nums) {int maxSum Integer.M…

Java 享元模式:打造高扩展游戏角色模型,优化 MMO 游戏开发

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

【精选】AI Coding 新范式:Windsurf、Cursor、Coze齐上阵

2AGI.NET | 探索 AI 无限潜力&#xff0c;2AGI 为您带来最前沿资讯。 随着人工智能技术的飞速发展&#xff0c;AI Coding领域迎来了前所未有的变革。Codeium的Windsurf、Cursor的agent模式更新、Copilot的新版本以及Coze的AI应用能力&#xff0c;都在推动着编程领域的创新。本期…

我的世界网易版安装Continuum光影

先去官网下载这个光影压缩包&#xff0c;下载完不要解压&#xff08;网慢的开加速器&#xff09; Downloads - Continuum Graphics 进去后选最新的版本号下载就行&#xff1a; 下载好后&#xff0c;先不管他&#xff0c;通过网易游戏启动器打开我的世界 觉得自己电脑性能将将…

使用uniapp开发小程序场景:在百度地图上调用接口返回的设备相关信息并展示

首先在百度地图开发者平台注册微信小程序开发密钥下载百度地图SDK-bmap-wx.min.js,下载地址在项目入口index.html页面进行引入页面中进行调用&#xff0c;代码示例如下<map id"map" longitude"108.95" latitude"34.34" scale"3" :m…

Push an existing folder和Push an existing Git repository的区别

Push an existing folder 和 Push an existing Git repository 是在使用 Git 服务&#xff08;如 GitHub、GitLab、Bitbucket 等&#xff09;时两个常见的操作选项。它们的区别主要体现在项目的初始化和版本控制状态上&#xff1a; 1. Push an existing folder 适用场景&#…

机器学习详解(2):线性回归之理论学习

文章目录 1 监督学习2 线性回归2.1 简单/多元线性回归2.2 最佳拟合线2.3 成本函数和梯度下降2.4 线性回归的假设2.5 线性回归的评估指标函数 3 总结 机器学习是人工智能的一个分支&#xff0c;主要致力于开发能够从数据中学习并进行预测的算法和统计模型。线性回归是机器学习的…