登录发现更多内容
首页
分类
发帖
账号
自动登录
找回密码
密码
登录
立即注册
立即登录
立即注册
其他登录
QQ
微信
首页
Portal
求助问答
Xiuno资源
Xiuno教程
Xiuno插件
Xiuno主题
休闲茶馆
定制主题
产品教程
BBS
求助问答
Xiuno资源
Xiuno教程
Xiuno插件
Xiuno主题
休闲茶馆
定制主题
开发资料
求助问答
Xiuno资源
Xiuno教程
Xiuno插件
Xiuno主题
休闲茶馆
定制主题
样品购买
求助问答
Xiuno资源
Xiuno教程
Xiuno插件
Xiuno主题
休闲茶馆
定制主题
IoT云平台
求助问答
Xiuno资源
Xiuno教程
Xiuno插件
Xiuno主题
休闲茶馆
定制主题
GitHub
求助问答
Xiuno资源
Xiuno教程
Xiuno插件
Xiuno主题
休闲茶馆
定制主题
技术博客
求助问答
Xiuno资源
Xiuno教程
Xiuno插件
Xiuno主题
休闲茶馆
定制主题
搜索
搜索
热搜:
LoRa
ESP8266
安信可
本版
帖子
用户
请
登录
后使用快捷导航
没有账号?
立即注册
每日签到
任务
广播
导读
排行榜
设置
我的收藏
退出
3
0
0
首页
技术杂谈
›
C语言多线程学习记录(一)
返回列表
C语言多线程学习记录(一)
[ 复制链接 ]
发布帖子
可乐klelee
金牌会员
9
主题
24
回帖
1473
积分
金牌会员
金牌会员, 积分 1473, 距离下一级还需 1527 积分
金牌会员, 积分 1473, 距离下一级还需 1527 积分
积分
1473
私信
3人留言
楼主
技术杂谈
1219
3
2023-9-15 20:40:45
在Linux内核中多线程编程是很常见的一种性能提升手段。所以学习C语言,对于多线程也应该有一定的了解。关于线程与进程,在知乎看到下面这么一段说明,觉得很清晰: 做个简单的比喻:进程=火车,线程=车厢 - 线程在进程下行进(单纯的车厢无法运行) - 一个进程可以包含多个线程(一辆火车可以有多个车厢) - 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘) - 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易) - 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源) - 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢) - 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上) - 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁" - 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量” ## 创建线程 创建线程的函数是:`pthread_create()` ,该函数声明在`pthread.h` 头文件中,其函数声明为: ```c int pthread_create( pthread_t *thread, const pthread_attr_t *attr, void *(*start_routing)(void *), void *arg); ``` ### 参数 该函数需要四个参数,其含义如下: - `pthread_t *thread` 一个指向 “新创建线程” 的指针变量 - `const pthread_attr_t *attr` 一个指向 “新创建线程的相关属性” 的指针变量,一般情况下不需要手动更改新建线程的属性,直接传NULL即可 - `void *(*start_routing)(void *)` 一个指向 “新线程要执行的函数” 的函数指针 - `void *arg` 代表传递给新线程要执行的函数的参数 ### 返回值 再次声明`pthread_create()` 函数的作用就是创建一个新的线程。其返回值就表示是否成功创建。所以其返回值有成功与不成功两种情况: - 成功创建: 返回 0 - 创建失败: 返回非0值,在`errno.h`头文件中定义了常见的几个异常返回值: - EAGIN:表示系统自远不足 - EINVAL: 表示attr参数无效 - EPERM: 表示attr参数非法,或者操作存在确权行为 ### 线程创建实例 ```c #include
#include
// sleep函数的头文件 #include
void * ThreadFun(void * arg) { // 新线程要执行的函数 if (arg == NULL) { printf("arg is NULL!\n"); }else { printf("%s\n", (char*)arg); } return NULL; } int main() { int res; char * url = "https://klelee.com"; pthread_t mythread1, mythread2; //定义两个新线程 /** * &mythread1 对应的形参是:pthread_t *thread,形参是指针变量,因此需要进行传址 * NULL 对应的形参是:const pthread_attr_t *attr,也就是新线程的属性 * ThreadFun 对应的形参是:void *(*start_routing)(void *), 执行新线程要执行的函数的函数指针 * NULL 对应的形参是:void *arg,新线程要执行的函数的参数 */ res = pthread_create(&mythread1, NULL, ThreadFun, NULL); if (res != 0) { printf("thread create failed!\n"); return 0; } /** * &mythread2 对应的形参是:pthread_t *thread,形参是指针变量,因此需要进行传址 * NULL 对应的形参是:const pthread_attr_t *attr,也就是新线程的属性 * ThreadFun 对应的形参是:void *(*start_routing)(void *), 执行新线程要执行的函数的函数指针 * NULL 对应的形参是:void *arg,新线程要执行的函数的参数 */ res = pthread_create(&mythread2, NULL, ThreadFun, (void*)url); if (res != 0) { printf("thread create failed!\n"); return 0; } sleep(5); return 0; } ``` ### 多线程程序的编译 需要注意的地方就是对于多线程程序,在编译的时候需要添加`lpthread` 选项 ``` gcc thread.c -o thread.out -lpthread ``` #### 执行结果 ) ## 终止线程 线程的终止,有三种情况: 1. 程序正常结束,线程终止 2. 通过return语句或者pthread_exit()终止线程 3. 其他线程发送pthread_cancel() 第一种情况就是字面意思,很容易理解。接下来详细说明后面两种情况。 ### 通过return和pthread_exit()终止进程 `pthread_exit()` 可以理解为是线程中的return语句。该函数也声明在`pthread.h` 文件中。其声明为: ```c void pthread_exit(void * retval); ``` 该函数返回值为空,有一个void类型的指针变量作为形式参数。这个参数就类似与return语句返回的数据一样,作为当前线程运行的函数的返回值。当没有返回值的时候,这里可以是NULL. #### return和pthread_exit()的区别 关于两者的区别还是直接体现在程序中吧,首先来看一个用return的程序: ```c #include
#include
#include
void * thread_func (void * arg) { sleep(5); printf("https://klelee.com"); } int main() { int res; pthread_t mythread; res = pthread_create(&mythread, NULL, thread_func, NULL); if (res != 0) { printf("new thread failed!\n"); return 0; } printf("main thread will return!\n"); return res; //主要体现在这里 } ``` 编译和运行结果如下: ``` [klelee@arch first]$ gcc exit_return.c -o exit_return.out -lpthread [klelee@arch first]$ ./exit_return.out main thread will return! [klelee@arch first]$ ``` 再来看看`pthread_exit()`的实例: ```c #include
#include
#include
void * thread_func (void * arg) { sleep(5); printf("https://klelee.com"); } int main() { int res; pthread_t mythread; res = pthread_create(&mythread, NULL, thread_func, NULL); if (res != 0) { printf("new thread failed!\n"); return 0; } printf("main thread will return!\n"); pthread_exit(NULL); // 将这里换成了pthread_exit() } ``` 运行结果: ``` [klelee@arch first]$ gcc exit_return.c -o exit_return.out -lpthread [klelee@arch first]$ ./exit_return.out main thread will return! [https://klelee.com](https://klelee.com) [klelee@arch first]$ ``` 对比如上两段程序的运行结果发现,区别就在于子线程中的程序有没有完全的执行。在主线程中直接使用return语句进行返回之后,由主线程创建的子线程也随之终止。 而使用pthread_exit语句的主线程终止之后,子线程没有终止,而是在继续运行,知道程序结束! ### pthread_cancel()函数 与上面两种程序终止方式不同的是,使用cancel信号终止是被动终止。调用cancel函数的其他线程,而不是线程自身调用。其生命如下: ```c int pthread_cancel(pthread_t *thread); ``` #### 参数 指向目标线程的指针变量 #### 返回值 有关于pthread_cancel函数的返回值需要注意的是其成功的条件:发送成功即为成功。即: - cancel信号成功发送给目标线程,返回0 - cancel信号发送失败,返回非0值。若未找到目标线程,返回ESRCH宏,该宏的值为3. 对于接收cancel信号后终止的线程,相当于自己调用了`pthread_exit(PTHREAD_CANCELED)` 其中宏:PTHREAD_CANCELED定义在`pthread.h` 头文件中。 ```c #include
#include
#include
void * thread_func (void * arg) { printf("https://klelee.com\n"); sleep(5); // 在这段时间内程序被终止 printf("cclike.cc\n"); // 主要看这句会不会输出 } int main() { int res; int value; void * mess; pthread_t mythread; res = pthread_create(&mythread, NULL, thread_func, NULL); if (res != 0) { printf("new thread failed!\n"); return 0; } //创建线程之后sleep两秒 sleep(2); // 然后立即发送cancel信号,在thread_fun函数睡眠的5秒内,强制终止程序运行 res = pthread_cancel(mythread); if (res != 0) { printf("stop thread failed!\n"); return 0; } res = pthread_join(mythread, &mess); if (res != 0) { printf("wait for thread failed!\n"); return 0; } if (mess == PTHREAD_CANCELED) { printf("force stop thread sucess!\n"); // 判断是否有cancel信号终止 }else { printf("force stop thread failed!\n"); } printf("main thread will return!\n"); pthread_exit(NULL); } ``` 编译和运行结果: ``` [klelee@arch first]$ gcc exit_return.c -o exit_return.out -lpthread [klelee@arch first]$ ./exit_return.out [https://klelee.com](https://klelee.com) force stop thread sucess! main thread will return! [klelee@arch first]$ ``` 通过运行结果可以看到,thread_fun()函数被提前终止了! ## cancel信号处理机制 并不是所有的cancel信号都会被线程处理。很常见的现象就是当目标线程接收到cancel信号的时候,正处于循环当中,这种情况就不会立即响应cancel信号去终止线程 事实上,对于默认属性的线程,当有线程借助 pthread_cancel() 函数向它发送 Cancel 信号时,它并不会立即结束执行,而是选择在一个适当的时机结束执行。 所谓适当的时机,POSIX 标准中规定,当线程执行一些特殊的函数时,会响应 Cancel 信号并终止执行,比如常见的 pthread_join()、pthread_testcancel()、sleep()、system() 等,POSIX 标准称此类函数为“cancellation points”(中文可译为“取消点”)。 此外,
头文件还提供有 pthread_setcancelstate() 和 pthread_setcanceltype() 这两个函数,我们可以手动修改目标线程处理 Cancel 信号的方式。 ### pthread_setcancelstate()函数 借助 pthread_setcancelstate() 函数,我们可以令目标线程处理 Cancal 信号,也可以令目标线程不理会其它线程发来的 Cancel 信号。其函数声明如下: ```c int pthread_setcancelstate( int state , int * oldstate ); ``` #### 参数 1. state 参数有两个可选值,分别是: 1. PTHREAD_CANCEL_ENABLE(默认值):当前线程会处理其它线程发送的 Cancel 信号; 2. PTHREAD_CANCEL_DISABLE:当前线程不理会其它线程发送的 Cancel 信号,直到线程状态重新调整为 PTHREAD_CANCEL_ENABLE 后,才处理接收到的 Cancel 信号。 2. oldtate 参数用于接收线程先前所遵循的 state 值,通常用于对线程进行重置。如果不需要接收此参数的值,置为 NULL 即可。 #### 返回值 pthread_setcancelstate() 函数执行成功时,返回数字 0,反之返回非零数。 ### pthread_setcanceltype()函数 当线程会对 Cancel 信号进行处理时,我们可以借助 pthread_setcanceltype() 函数设置线程响应 Cancel 信号的时机。语法格式如下: ```c int pthread_setcanceltype( int type , int * oldtype ); ``` #### 参数 (1) type 参数有两个可选值,分别是: - PTHREAD_CANCEL_DEFERRED(默认值):当线程执行到某个可作为取消点的函数时终止执行; - PTHREAD_CANCEL_ASYNCHRONOUS:线程接收到 Cancel 信号后立即结束执行。 (2) oldtype 参数用于接收线程先前所遵循的 type 值,如果不需要接收该值,置为 NULL 即可。 #### 返回值 pthread_setcanceltype() 函数执行成功时,返回数字 0,反之返回非零数。 #### 实例 ```c #include
#include
#include
void * thread_Fun(void * arg) { printf("新建线程开始执行\n"); int res; //设置线程为可取消状态 res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); if (res != 0) { printf("修改线程可取消状态失败\n"); return NULL; } //设置线程接收到 Cancel 信号后立即结束执行 res = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); if (res != 0) { printf("修改线程响应 Cancel 信号的方式失败\n"); return NULL; } while (1); return NULL; } int main() { pthread_t myThread; void * mess; int value; int res; res = pthread_create(&myThread, NULL, thread_Fun, NULL); if (res != 0) { printf("线程创建失败\n"); return 0; } sleep(1); //向 myThread 线程发送 Cancel 信号 res = pthread_cancel(myThread); if (res != 0) { printf("终止 myThread 线程失败\n"); return 0; } //等待 myThread 线程执行结束,获取返回值 res = pthread_join(myThread, &mess); if (res != 0) { printf("等待线程失败\n"); return 0; } if (mess == PTHREAD_CANCELED) { printf("myThread 线程被强制终止\n"); } else { printf("error\n"); } return 0; } ``` 编译与运行结果 ``` [root@localhost ~]# gcc thread.c -o thread.exe -lpthread [root@localhost ~]# ./thread.exe 新建线程开始执行 myThread 线程被强制终止 ``` ## 获取线程函数的返回值 在上面的函数中我们用到了一个新的函数`pthread_join` ,该函数能够获取某个线程执行结束时返回的数据。接下来我们详细看看它的使用方法。 ### pthread_join函数 该函数声明在`pthread.h`头文件中,其函数声明如下: ```c int pthread_join(pthread_t thread, void ** retval); ``` #### 参数 该函数有两个返回值: - `thread` 参数用于指定接收哪个线程的返回值 - `retval` 这里用一个二级指针直线了返回值地址的地址。如果 thread 线程没有返回值,又或者我们不需要接收 thread 线程的返回值,可以将 retval 参数置为 NULL。 pthread_join() 函数会一直阻塞调用它的线程,直至目标线程执行结束(接收到目标线程的返回值),阻塞状态才会解除。 #### 返回值 该函数拥有int类型的返回值。 - 0 值: 表示函数成功等到目标线程运行结束(成功拿到返回值)。 - 非零值:也就是等待目标线程返回失败,不同原因有不同的返回值,常见原因如下: - EDEADLK:检测到线程发生了死锁。 - EINVAL:分为两种情况,要么目标线程本身不允许其它线程获取它的返回值,要么事先就已经有线程调用 pthread_join() 函数获取到了目标线程的返回值。 - ESRCH:找不到指定的 thread 线程。 相关宏都在errno.h中做了定义!
点赞
0
收藏
0
淘帖
0
────
0
人觉得很赞
────
回复
使用道具
举报
3 回复
电梯直达
正序浏览
倒序浏览
正序浏览
沙发
WangChong
回复
使用道具
举报
2023-9-15 20:52:15
有学习资料吗
回复
评论
使用道具
举报
板凳
可乐klelee
楼主
回复
使用道具
举报
2023-9-15 21:06:19
WangChong 发表于 2023-9-15 20:52
有学习资料吗
C语言中文网C语言部分讲的还不错,但是好多都要会员。
回复
评论
使用道具
举报
地板
WYG
回复
使用道具
举报
2023-9-15 21:39:04
打卡
回复
评论
使用道具
举报
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
立即登录
手机登录
点评
高级模式
本版积分规则
回帖并转播
回帖后跳转到最后一页
返回
今日推荐
求助——AiPi-PalChatV1通过UART-MCP控制梁山派(GD32)LED失败
星闪怎么下架了
【求助】D200连接官方烧录底板 烧录握手失败
基于AI-WB2实现MQTTS(MQTT-SSL)单向+双向加密传输
基于Ai-WB2实现使用MQTT完成订阅、发布及点灯功能
BW20-12F-KIT usbd_inic_dplus问题
基于Ai-WB2的HomeAssistant实现RGB彩灯控制功能
BU04 原理图
基于 Ai-WV01-32S+STM32移植 emMCP 实现 AI 语音控制点灯
AiPi-PalChatV1_“湾湾小何”提示音测试固件V2.9_UART-MCP
热帖排行
求助——AiPi-PalChatV1通过UART-MCP控制梁山派(GD32)LED失败
求助-BLE模块接收广播数据问题
[BW20] 5G信号强度问题提问
星闪怎么下架了
esp8266不能连接问题
Ai-WB2-01S烧录固件进度到100后失败,开机后无反应
BW20-12F SPI Port
开发板eyes-s1求助
统计信息
会员数: 30703 个
话题数: 44755 篇
首页
分类
我的