linux——多线程,线程控制

目录

一.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()
{//线程idpthread_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()
{// 线程idpthread_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()
{// 线程idpthread_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()
{// 线程idpthread_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()
{// 线程idpthread_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.hqwc.cn/news/136444.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBean的初始化流程

当我们启动Spring容器后&#xff0c;会先通过AbstractApplicationContext#refresh方法&#xff0c;调用BeanFactoryPostProcess方法&#xff0c;可以在bean初始化前&#xff0c;修改context中的BeanDefinition&#xff0c;但是因为此时Bean还没有初始化&#xff0c;所以并不会修…

C与C++之间相互调用的基本方法

​ 在你的C语言代码中&#xff0c;不知能否看到类似下面的代码&#xff1a; 这好像没有什么问题&#xff0c;你应该还会想&#xff1a;“嗯⋯是啊&#xff0c;我们的代码都是这样写的&#xff0c;从来没有因此碰到过什么麻烦啊&#xff5e;”。 你说的没错&#xff0c;如果你的…

【LeetCode】19. 删除链表的倒数第 N 个结点

1 问题 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例…

websocket逆向-protobuf序列化与反序列化

系列文章目录 训练地址&#xff1a;https://www.qiulianmao.com 基础-websocket逆向基础-http拦截基础-websocket拦截基础-base64编码与解码基础-protobuf序列化与反序列化视频号直播弹幕采集tiktok protobuf序列化与反序列化实战一&#xff1a;Http轮询更新中 websocket逆向-…

Qt之submodule编译

工作中会遇到这样一种情况&#xff1a;qt应用程序在运行时提示找不到某个qt的动态库。我遇到的是缺少libQt5Websocket.so&#xff0c;因为应用程序是在x86平台银河麒麟v10上开发&#xff0c;能够正常编译运行&#xff0c;然后移植到rk3588&#xff08;aarch64架构&#xff09;上…

C++ wpf自制软件打包安装更新源码实例

程序示例精选 C wpf自制软件打包安装更新源码实例 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《C wpf自制软件打包安装更新源码实例》编写代码&#xff0c;代码整洁&#xff0c;规则&…

路由router

什么是路由? 一个路由就是一组映射关系&#xff08;key - value&#xff09;key 为路径&#xff0c;value 可能是 function 或 component 2、安装\引入\基础使用 只有vue-router3&#xff0c;才能应用于vue2&#xff1b;vue-router4可以应用于vue3中 这里我们安装vue-router3…

stm32学习笔记:EXIT中断

1、中断系统 中断系统是管理和执行中断的逻辑结构&#xff0c;外部中断是众多能产生中断的外设之一。 1.中断&#xff1a; 在主程序运行过程中&#xff0c;出现了特定的中断触发条件 (中断源&#xff0c;如对于外部中断来说可以是引脚发生了电平跳变&#xff0c;对于定时器来…

Puppeteer结合测试工具jest使用(四)

Puppeteer结合测试工具jest使用&#xff08;四&#xff09; Puppeteer结合测试工具jest使用&#xff08;四&#xff09;一、简介二、与jest结合使用&#xff0c;集成到常规测试三、支持其他的几种四、总结 一、简介 Puppeteer是一个提供自动化控制Chrome或Chromium浏览器的Node…

windows10系统-16-制作导航网站WebStack-Hugo

上个厕所功夫把AI导航搞定了 使用Hugo搭建静态站点 如何使用Hugo框架搭建一个快如闪电的静态网站 1 Hugo 参考Hugo中文文档 参考使用Hugo搭建个人网站 Hugo是由Go语言实现的静态网站生成器。简单、易用、高效、易扩展、快速部署。 1.1 安装Hugo 二进制安装&#xff08;推荐…

Android 内容提供者和内容观察者:数据共享和实时更新的完美组合

任务要求 一个作为ContentProvider提供联系人数据另一个作为Observer监听联系人数据的变化&#xff1a; 1、创建ContactProvider项目&#xff1b; 2、在ContactProvider项目中用Sqlite数据库实现联系人的读写功能&#xff1b; 3、在ContactProvider项目中通过ContentProvid…

Linux网络编程系列之UDP组播

Linux网络编程系列 &#xff08;够吃&#xff0c;管饱&#xff09; 1、Linux网络编程系列之网络编程基础 2、Linux网络编程系列之TCP协议编程 3、Linux网络编程系列之UDP协议编程 4、Linux网络编程系列之UDP广播 5、Linux网络编程系列之UDP组播 6、Linux网络编程系列之服务器编…