Linux线程--线程创建、等待及退出

1. pthread_create

功能

创建一个新的线程。

原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数
  • thread: 指向pthread_t类型变量的指针,用于存储新创建线程的标识符。
  • attr: 线程属性,通常为NULL表示默认属性。如果需要指定线程属性,可以使用pthread_attr_t结构。
  • start_routine: 新线程的入口函数,即新线程将从这个函数开始执行。线程开始执行的函数类型为void *(*start_routine)(void *),它接受一个void *参数并返回一个void *值。
  • arg: 传递给新线程入口函数的参数。
返回值
  • 成功:返回0。
  • 失败:返回错误代码。
pthread_t t1;
int param = 100;
int ret = pthread_create(&t1, NULL, func1, (void *)&param);
if (ret != 0) {printf("Error creating thread: %d\n", ret);
}

2. pthread_self

功能:获取调用线程的线程标识符。
原型
pthread_t pthread_self(void);
参数:无。
返回值:返回调用线程的线程标识符。

用途: 常用于调试、日志记录以及需要区分线程的情景。

pthread_t tid = pthread_self();
printf("Current thread ID: %ld\n", (unsigned long)tid);

3. pthread_join

功能:等待指定的线程终止。pthread_join会阻塞调用它的线程,直到指定的线程结束。这对于确保所有线程在主线程退出之前完成工作非常有用。
原型
int pthread_join(pthread_t thread, void **retval);
参数
  • thread: 要等待的线程的标识符。
  • retval: 存储线程返回值的指针。如果不需要获取线程的返回值,可以传递NULL。如果需要获取线程的返回值,retval参数不能为NULL,应该传递一个指向指针的指针。
返回值
  • 成功:返回0。
  • 失败:返回错误代码。
示例
pthread_t t1;
// 创建线程 t1
pthread_create(&t1, NULL, func1, (void *)&param);// 等待线程 t1 结束
pthread_join(t1, NULL);

4. pthread_exit

功能:终止调用
它的线程。该函数用于显式地终止线程,通常用于线程需要提前结束的情况。

自动清理: 调用pthread_exit的线程会自动释放分配给它的资源。

原型
void pthread_exit(void *retval);
参数
  • retval: 线程的返回值,传递给pthread_joinretval参数。
返回值

无,因为此函数不会返回到调用线程。

示例
void *func1(void *arg) {printf("Thread is exiting.\n");pthread_exit(NULL);
}

示例代码

结合上述函数,这里做个演示

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void * func1(void *arg)
{//关于为什么要使用static,请看下面的解释static char *p = "t1 is run"; //为什么转换成unsigned long,请看下面的解释printf("func1: %ld\n", (unsigned long)pthread_self());//退出当前线程,并返回一个指针给pthread_joinpthread_exit((void *)p);
}int main()
{//定义一个线程标识符pthread_t t1;int data = 100;char *pret = NULL;int ret = pthread_create(&t1, NULL, func1, (void *)&data);if(ret == 0){printf("successfully creat a pthread!\n");}else{printf("faild to creat a pthread\n");}printf("I am main: %ld\n", (unsigned long)pthread_self());pthread_join(t1, (void **)&pret);printf("main: t1 quit: %s\n", pret);return 0;
}

问题1:为什么要使用static静态变量?

非静态变量的行为

考虑一个没有使用 static 的情况:

#include <stdio.h>void *func1(void *arg) {char p[] = "t1 is run";  // 非静态局部变量return (void *)p;
}int main() {void *result = func1(NULL);printf("Result: %s\n", (char *)result);  // 未定义行为,可能导致错误return 0;
}

在这个例子中:

  • char p[] 是一个局部数组变量,存储在栈中。
  • func1 返回时,p 指向的内存可能已无效,因为函数返回后栈帧被销毁。
  • 访问返回的指针会导致未定义行为,可能会崩溃或输出垃圾值。

         C 语言标准中规定,对于局部变量(自动变量),当函数返回时,它们的内存会被释放。如果你返回一个指向局部变量的指针,这个指针会指向无效内存。虽然在某些情况下,内存内容可能未被立即覆盖或销毁,因此可以“侥幸”地正确打印,但这种做法是不安全的。

使用静态变量

现在使用 static 关键字:

#include <stdio.h>void *func1(void *arg) {static char p[] = "t1 is run";  // 静态局部变量return (void *)p;
}int main() {void *result = func1(NULL);printf("Result: %s\n", (char *)result);  // 正常输出 "t1 is run"return 0;
}

在这个例子中:

  • static char p[] 是一个静态变量,存储在静态数据区而不是栈中。
  • 变量 p 在第一次调用时初始化,并在程序的整个运行期间保留其值和内存地址。
  • func1 返回时,返回的指针 p 始终有效,可以安全地访问。

关键点总结

  • 持久性:静态变量在整个程序运行期间存在,不会因函数返回而销毁。
  • 唯一初始化:静态变量只在第一次声明时初始化,后续调用不会重新初始化。
  • 作用域限制:静态局部变量的作用域限制在函数内部,无法被外部访问。

使用静态变量的场景

静态变量适用于以下场景:

  1. 返回局部数据的地址:当函数需要返回局部数据的地址时,可以使用静态变量确保数据在函数返回后依然有效。
  2. 保持函数调用间的状态:当函数需要在多次调用之间保持某些状态时,使用静态变量可以实现状态的持久化。

 问题2:为什么将pthread_self()的返回值转换为unsigned long类型?

  1. 不透明类型pthread_t 是一个不透明类型,其实现依赖于具体的操作系统和线程库。在某些系统上,pthread_t 可能是一个整数类型,在其他系统上可能是一个指针类型。
  2. 类型大小不同:不同系统上,pthread_t 的大小可能不同(例如,32位系统和64位系统上类型大小可能不同)。

使用 unsigned long 的理由

  1. 通用格式化unsigned long 是一种标准的整数类型,在C语言中占用固定的字节数(通常是32位或64位)。这使得它在大多数系统上都可以以一种一致的方式打印。
  2. 格式化支持printf 函数支持 %lu 格式说明符来打印 unsigned long 类型,这在C语言标准库中是通用的。
  3. 避免类型问题:将 pthread_t 转换为 unsigned long 可以避免由于不同系统上 pthread_t 类型不同带来的编译和运行时问题。

问题3:关于线程函数

void *thread_func(void *arg) {}pthread_create(&t1, NULL, thread_func, (void *)&param);
  • 线程函数: thread_func 是符合 void *(*)(void *) 类型的函数,它接受一个 void * 类型的参数,并返回一个 void * 类型的值。

  • 创建线程: pthread_create(&t1, NULL, thread_func, (void *)&param);

    • 第一个参数 &t1 是指向线程标识符的指针。
    • 第二个参数 NULL 表示使用默认线程属性。
    • 第三个参数 thread_func 是线程函数的指针。
    • 第四个参数 (void *)&param 是传递给线程函数的参数,强制转换为 void * 类型。为什么要强制转换呢,接着往下看问题4,就明白了。

 问题4:pthread_create的第三个参数类型void *(*start_routine)(void *),怎么去理解?

我们可以拆分来理解:

  1. void *: 表示函数的返回类型是一个void *指针。线程函数可以返回任意类型的数据,通过指针的方式返回。

  2. (*start_routine): 表示这是一个指向函数的指针,指向的函数名为start_routine

  3. (void *): 表示这个函数接受一个void *类型的参数。这个参数可以是任意类型的数据,通过指针的方式传递。

void *(*start_routine)(void *) 表示一个指向函数的指针(*start_routine),这个函数接受一个void *类型的参数(void *),并返回一个void *类型的值。也就是说对应的函数要为指针函数。

问题5:关于多次使用到void *

void * 类型

void * 是一种通用指针类型,可以指向任意类型的数据。它不携带任何类型信息,这意味着你可以将任何类型的数据指针转换为 void *,并且可以从 void * 类型转换回原始类型的指针。

为什么使用 void *

在需要传递不确定类型的数据时,void * 非常有用。例如,在多线程编程中,你可能希望线程函数能够处理不同类型的参数,而 void * 允许你传递任意类型的数据指针。

问题6:pthread_join的第二个参数

pthread_join 内部如何工作

pthread_join 的实现中,内部逻辑大概如下:

  1. 等待线程结束:主线程会阻塞等待子线程 t1 结束。
  2. 获取线程的返回值:当线程调用 pthread_exit((void *)p) 退出时,返回的指针 p 会被传递回 pthread_join
  3. 存储返回值pthread_join 将返回的指针 p 存储在 retval 所指向的地址中。

        在 pthread_join 调用中传递 &pret 的目的是让线程库在 pthread_join 内部通过这个指针来修改 pret 的值(意思是把pret的地址穿给线程库,线程库知道了pret在哪,然后才能找到pret去修改pret的值),使 pret 指向线程函数返回的地址。意思是把pret的地址穿给线程库,线程库知道了pret在哪,然后才能找到pret去修改pret的值。

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

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

相关文章

采用bat结合zabbix监控sqlserver数据库表的信息

采用bat结合zabbix监控sqlserver数据库表的信息&#xff0c;当表插入某个特定的值的时候就发出告警。 监控需求&#xff1a;数据库存在某个表&#xff0c;该表不是一个固定的表名字&#xff0c;而且根据当前的日期生成表&#xff0c;比如tab20240501,tab20240502,需要查询当天…

安卓init进程详解

目录 一、概述1.1 Init进程如何被启动&#xff1f;1.2 Init进程启动后&#xff0c;做了哪些事&#xff1f; 二、kernel启动init进程2.1 kernel_init2.2 do_basic_setup 三、Init 进程启动源码分析3.1 Init 进程入口3.2 ueventd_main3.3 init 进程启动第一阶段3.4 加载SELinux规…

FLINK-窗口算子

参考资料 官方文档- WindowFlink中的时间和窗口之窗口 窗口 在流处理中&#xff0c;我们往往需要面对的是连续不断、无休无止的无界流&#xff0c;不可能等到所有所有数据都到齐了才开始处理。所以聚合计算其实只能针对当前已有的数据——之后再有数据到来&#xff0c;就需要继…

chat4-Server端保存聊天消息到mysql

本文档描述了Server端接收到Client的消息并转发给所有客户端或私发给某个客户端 同时将聊天消息保存到mysql 服务端为当前客户端创建一个线程&#xff0c;此线程接收当前客户端的消息并转发给所有客户端或私发给某个客户端同时将聊天消息保存到mysql 本文档主要总结了将聊天…

多项目的.net core解决方案(项目间引用)如何使用Docker部署

解决方案内部项目之间引用很正常&#xff0c;但我docker不是很熟&#xff0c;对一些基础命令含义还理解不深入&#xff0c;部署引用其他项目的项目总不成功。搜到了一篇非常适合初学者&#xff0c;从dockerfile命令讲解&#xff0c;到解决引用其他项目时如何docker部署的文章。…

JavaWeb_SpringBootWeb基础

先通过一个小练习简单了解以下SpringBootWeb。 小练习&#xff1a; 需求&#xff1a;使用SpringBoot开发一个Web应用&#xff0c;浏览器发起请求/hello后&#xff0c;给浏览器返回字符串"Hello World~"。 步骤&#xff1a; 1.创建SpringBoot项目&#xff0c;勾选We…

3-EMMC命令使用

在调试emmc的过程&#xff0c;我们需要用到命令读写emmc&#xff0c;烧录&#xff0c;查看emmc寄存器&#xff0c;设置寄存器等功能&#xff0c;所以uboot和linux下都有各自的命令可以使用。 1、 uboot下mmc命令 1.1、mmc信息 查看mmc信息&#xff1a;mmc info 描述了emmc的速…

epoll模型下的简易版code

epoll模型下的简易版code c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/epoll.h> #include <fcntl.h>#define MAX_EVENTS 10 #define NUM_DESCRIPTORS 5 // 模拟多个文件描述符// …

【代码随想录训练营】【Day 38】【贪心-5】| Leetcode 435, 763, 56

【代码随想录训练营】【Day 38】【贪心-5】| Leetcode 435, 763, 56 需强化知识点 重叠区间系列 题&#xff0c; 763&#xff0c; 435 题目 435. 无重叠区间 左起点排序&#xff0c;记录重叠区间个数&#xff0c;总数相减即为结果&#xff0c;过程中维护右边界注意&#x…

工具类解决事务和过滤器解决事务

事务的四大特性ACID 原子性&#xff1a;强调事务的不可分割.多条语句要么都成功&#xff0c;要么都失败。 一致性&#xff1a;强调的是事务的执行的前后&#xff0c;数据要保持一致 隔离性&#xff1a;并发访问数据库时,一个事务的执行不应该受到其他事务的干扰. 持久性&#…

测试:ollama加载羊驼版本llama-3中文大模型

找了一个晚上各种模型&#xff0c;像极了当初找各种操作系统的镜像&#xff0c;雨林木风&#xff0c;深蓝、老毛桃…… 主要是官方的默认7B版本回答好多英文&#xff0c;而且回复的很慢&#xff0c;所以我是在ollama上搜索"chinese"找到了这个羊驼版本的&#xff0c…

使用javacv对摄像头视频转码并实现播放

要实现Java接受RTSP流解码&#xff0c;并推送给前端实现播放实时流&#xff0c;可以使用一些流媒体处理库&#xff0c;比如JavaCV或者FFmpeg等。以下是一个简单的示例代码&#xff1a; 1.控制层方面的 根据视频rtsp流链接打开转换&#xff0c;通过响应写出流到前台使用flvjs播…

go语言初学04

Go 语言近年来发展迅速&#xff0c;并且出现了许多优秀的开发框架和组件来支持各种不同的开发需求。以下是一些常用的 Go 语言开发框架和组件&#xff1a; Web 框架 Gin&#xff1a; URL: Gin简单、高效、易用&#xff0c;适合构建高性能的 Web 应用。 Echo&#xff1a; URL: …

crossover软件是干什么的 crossover软件安装使用教程 crossover软件如何使用

CrossOver 以其出色的跨平台兼容性&#xff0c;让用户在Mac设备上轻松运行各种Windows软件&#xff0c;无需复杂的设置或额外的配置&#xff0c;支持多种语言&#xff0c;满足不同国家和地区用户的需求。 CrossOver 软件是干嘛的 使用CrossOver 不必购买Windows 授权&#xf…

Winform ListView 嵌入组合框、布尔、图片等复杂控件

一、Winform ListView 显示复杂控件示例 以下展示了两种实现思路方案。最后修改日期 2024-05 surfsky 1.1 方案一&#xff1a;ListView 结合组合框进行模拟编辑 基本思路 在界面上放置一个lisview和一个combobox&#xff0c;combobox平时是隐藏的。点击listview&#xff0c…

ArrayList源码讲解

ArrayList 底层采用的是数组队列&#xff0c;相当于动态数组。 ArrayList内部使用一个可重新分配的Object数组来存储元素&#xff0c;这个数组会随着元素的添加自动增长以容纳更多的元素&#xff0c;这就是所谓的“动态数组”。 1.实现了RandomAccess接口&#xff0c;可以随机…

rust嵌入式开发之总结

我们用rust开发的新版产品刚刚交付&#xff0c;已经在海上安装测试完毕并顺利投产。终于松了口气&#xff0c;同时也有时间和精力来做个全面的总结了。 这个产品&#xff0c;目前差不多有三版&#xff1a; 第一个版本是用crt-thread写的&#xff0c;投产后出了一个内存泄露的…

521源码-源码论坛-宝塔面板操作日志是存放在哪里的? 如何删除部分日志记录

我们帮别人搭建或者登录了&#xff08;不是自己权属的宝塔面板&#xff09;&#xff0c;会留下登录及操作的日志&#xff0c;我们不想留下这些操作日志&#xff0c;可以通过下面的方法处理掉&#xff0c;以达到无痕迹访问操作的目的&#xff1a; 如图所示的面板操作日志&#…

Python-3.12.0文档解读-内置函数sum()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 sum(iterable, /, start0) 参数&#xff1a; 返回值&#xff1a; 注意事…

骑砍2霸主MOD开发(10)-游戏大地图(MapScene)制作

一.MapScene中初始化NavMeshFaceID与TerrainType public TerrainType GetFaceTerrainType(PathFaceRecord navMeshFace){switch (navMeshFace.FaceGroupIndex){case 1:return TerrainType.Plain;case 2:return TerrainType.Desert;case 3:return TerrainType.Snow;case 4:retur…