Linux系统编程(五)多线程创建与退出

目录

  • 一、基本知识点
  • 二、线程的编译
  • 三、 线程相关函数
    • 1. 线程的创建
      • (1)整型的传入与接收
      • (2)浮点数的传入与接收
      • (3)字符串的传入与接收
      • (4)结构体的传入与接收
    • 2. 线程的退出
    • 3. 线程的等待
    • 补充
      • (1)返回整型
      • (2)返回浮点数
      • (3)返回字符串
      • (4)返回结构体
  • 四、综合举例

  

一、基本知识点

  1. 定义
       线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。每一个进程都至少包含一个main线程。
       相比于进程而言,线程更加轻量级,因为它们共享了进程的地址空间以及其他资源,所以线程之间的切换和通信会更加高效。一个进程可以包含多个线程,这些线程可以并发执行,各自独立完成一些特定的任务,或者共同完成一个复杂的任务。进程里每个线程执行的顺序不一定。主线程结束,则所有线程结束。
    在这里插入图片描述

  2. 共享资源保护
       由于某一线程与同属一个进程的其他的线程共享进程的资源,如内存空间、文件描述符和其他一些进程相关的属性。所以在多线程编程中,通常会涉及到共享资源保护操作,如互斥锁等以确保线程之间的协调运行,避免资源竞争和数据不一致的问题。即每次对共享资源进行操作时,只能有一个线程操作,其他线程必须等待操作完毕后,才能对其继续操作。

二、线程的编译

   Linux 的线程是通过用户级的函数库实现的,一般采用 pthread 线程库实现线程的访问和控制。它用第 3 方 posix 标准的 pthread,具有良好的可移植性。在使用了线程的代码编译的时候要在后面加上 -lpthread

例如:gcc test.c -o test -lpthread

三、 线程相关函数

头文件:#include <pthread.h>

在这里插入图片描述

1. 线程的创建

int pthread_create(pthread_t* thread,  pthread_attr_t * attr, void *(*start_routine)(void *),  void * arg);
//pthread_t* thread :线程的句柄,用于区分是哪个线程。
//pthread_attr_t * attr :线程的属性,通常为NULL。
//void *(*start_routine)(void *) :线程所执行的函数,该函数形参和返回值都要为void *。
// void * arg :该值用于传递第三个参数函数的形参,如果不传递可以为NULL。

举例:
在这里插入图片描述

(1)整型的传入与接收

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *task(void *arg)
{int *num=(int *)arg;while(1){sleep(1); printf("我是子线程,传入参数为%d\n",*num);}
}int main()
{int num =100;pthread_t  thread; //定义线程句柄pthread_create(&thread, NULL, task, (void *)&num);  //创建线程,并绑定线程函数。  while(1){sleep(1); printf("我是主线程\n");}
}

在这里插入图片描述

(2)浮点数的传入与接收

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *task(void *arg)
{float*num=(float *)arg;while(1){sleep(1); printf("我是子线程,传入参数为%f\n",*num);}
}int main()
{float num =10.10;pthread_t  thread; //定义线程句柄pthread_create(&thread, NULL, task, (void *)&num);  //创建线程,并绑定线程函数。  while(1){sleep(1); printf("我是主线程\n");}
}

在这里插入图片描述

(3)字符串的传入与接收

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *task(void *arg)
{char *num=(char *)arg;while(1){sleep(1); printf("我是子线程,传入参数为%s\n", num);}
}int main()
{char *string="i love you!";pthread_t  thread; //定义线程句柄pthread_create(&thread, NULL, task, (void *)string);  //创建线程,并绑定线程函数。  while(1){sleep(1); printf("我是主线程\n");}
}

在这里插入图片描述

(4)结构体的传入与接收

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>struct people{char *name;int age;
};void *task(void *arg)
{struct people *student=(struct people*)arg;while(1){sleep(1); printf("我是子线程,传入参数名字:%s,年龄:%d\n", student->name,student->age);}
}int main()
{struct people *student;pthread_t  thread; //定义线程句柄//将结构体赋值student->age=10;strcpy(student->name, "John"); pthread_create(&thread, NULL, task, (void *)student);  //创建线程,并绑定线程函数。  while(1){sleep(1); printf("我是主线程\n");}
}

在这里插入图片描述

2. 线程的退出

   函数 pthread_exit 表示线程的退出。其传入的的参数可以被其它线程用 pthread_join 等待函数进行捕获。例如: pthread_exit( (void*)3 ); 表示线程退出,并将 3作为返回值被后面的 pthread_join 函数捕获。该函数的作用就是可以将该线程的计算结果传递给创建它的主线程或者其他线程。
   注意1:在使用线程函数时,不能随意使用 exit 退出函数进行出错处理,由于 exit 的作用是使调用进程终止,往往一个进程包括了多个线程,所以在线程中通常使用 pthread_exit 函数来代替进程中的退出函数 exit。
   注意2:如果线程退出的返回值是在线程内部定义的局部变量的话,记得加static修饰,增加变量的生命周期直至进程结束而结束。不然线程退出后,该片内存地址会立刻被销毁,此时返回的空间无效。或者将返回值修改为全局变量。

void pthread_exit(void *retval);
//void *retval:作为线程退出时的值。如果不需要捕获该值,可以传入NULL。

3. 线程的等待

   当父线程结束的时候,如果没有 pthread_join 函数等待子线程执行的话,父线程会退出,而主线程的退出会导致进程的退出,故子线程也会退出。
   由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以通过 wait()函数系统调用来同步终止并释放资源一样,线程之间也有类似的机制,那就是 pthread_join 函数。这个函数是一个线程阻塞函数,调用这函数的线程将一直等待直到被等待的线程结束为止,当函数返回时,被等待线程的资源被回收。

int pthread_join(pthread_t pthid, void **thread_return);
//pthread_t pthid :线程句柄。
//void **thread_return : 用于接收线程退出的返回值。如果没有需要接收的填NULL。

   上述函数第二个参数为二级指针,所以我们可以定义一级指针再取地址来获取该值,然后再进行强制类型转换为(void **)。

举例:
在这里插入图片描述

在这里插入图片描述

思考:上述例程中为什么要用static来修饰变量呢?
   答:这是因为当线程退出时,其在内部定义的变量都会被销毁,可以将变量设为全局变量或者使用static来修饰变量,增加其生命周期,直至进程结束,才会被销毁。不然返回的地址将是一个已经被销毁的地址,无法使用。

补充

   一般线程的退出和线程的等待是一起使用的。这里我们使用几种参数类型进行举例。

(1)返回整型

//退出:
static int a=10;
pthread_exit((void *) &a);
//-------------------------------//等待接收:
int *thread_result;
pthread_join(thread,(void **)&thread_result);
printf("子线程的返回值为:%d\n", *thread_result);

(2)返回浮点数

//退出:
static float f=10.12;
pthread_exit((void *) &f);
//-------------------------------
//等待接收:
float *thread_result;
pthread_join(thread,(void **)&thread_result);
printf("子线程的返回值为:%f\n", *thread_result);

(3)返回字符串

//退出:
static char *string="i love you!";
pthread_exit((void *)string);
//-------------------------------
//等待接收:
char *thread_result;
pthread_join(thread,(void **)&thread_result);
printf("子线程的返回值为:%s\n", thread_result);

(4)返回结构体

typedef struct 
{char *name;  //定义指针,后续使用时要为其开辟内存空间! 如果不想开辟空间,则使用数组。int  age;
}people;//退出:people *student = (people *)malloc(sizeof(people)); //给结构体开辟空间,然后赋值。student->name=(char *)malloc(sizeof(char *)); //为结构体里定义的字符指针开辟内存空间。//给结构体赋值strcpy(student->name,"john");student->age=12;pthread_exit((void *)student);//等待接收student *thread_result;pthread_join(thread,(void **)&thread_result);printf("名字为:%s,年龄为:%d\n", thread_result->name,thread_result->age);

四、综合举例

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>void *task1(void * arg)    //线程1函数
{float *num =(float *)arg;while(1){sleep(1);printf("这是子线程1,传入的值为%f\n",*num);}  
}void *task2(void * arg)    //线程2函数
{int *num =(int *)arg;static char *string="pthread2_end";while(1){  sleep(1);printf("这是子线程2,传入的值为%d\n",*num);(*num)--;if(*num <0) break;}pthread_exit((void *)string);
}int main()
{float pthread1_arg=11.2;  //传入线程函数1的参数int pthread2_arg=3;  //传入线程函数2的参数char *string;int ret;pthread_t thread1,thread2;//线程句柄//创建线程1ret=pthread_create(&thread1, NULL, task1, (void *) &pthread1_arg);if(ret<0) {perror("pthread1_create error!\n");return -1;}//创建线程2ret=pthread_create(&thread2, NULL, task2, (void *) &pthread2_arg);if(ret<0) {perror("pthread2_create error!\n");return -1;}pthread_join(thread2, (void **)&string);printf("%s\n",string);return 0;
}

在这里插入图片描述

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

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

相关文章

FastGPT私有化部署+OneAPI配置大模型

介绍 FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景&#xff01; 官网地址 https://doc.fastai.site/docs/intro/ 部署 FastGPT提供…

如何实现数据的正确拆分?

我们知道在传统的单块架构中&#xff0c;一个系统中只存在一个独立的服务和数据库实例。 上图中的系统架构实现起来比较简单&#xff0c;但是扩展性和伸缩性都比较差。因此&#xff0c;越来越多的系统开始采用了微服务架构。在微服务架构中&#xff0c;一个系统被拆分成多个服务…

6种经典的网页布局设计,你最喜欢哪个?

信息时代&#xff0c;我们每天都会浏览很多网页&#xff0c;但你有没有想过&#xff0c;让你停留在一个新网页的关键因素有哪些&#xff1f;毫无疑问&#xff0c;网页布局一定是关键因素之一。一个优秀的网页布局不仅可以让网站看起来更美观、更专业&#xff0c;还能够抓住用户…

高效记录收支明细,预设类别账户,智能统计财务脉络,轻松掌握个人财务!

收支明细管理是每位个人或企业都必须面对的财务任务&#xff0c;财务管理已经成为我们生活中不可或缺的一部分。如何高效记录收支明细&#xff0c;预设类别账户&#xff0c;智能统计财务脉络&#xff0c;轻松掌握个人财务&#xff1f;晨曦记账本为您提供了完美的解决方案&#…

Java 面向对象编程(OOP)

面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是Java编程语言的核心思想之一。通过OOP&#xff0c;Java提供了一种结构化的编程方式&#xff0c;使代码更易于维护和扩展。 一、类和对象 1. 类的定义 类是对象的蓝图或模板&#xff0c;定…

File name ‘xxxx‘ differs from already included file name ‘xxxx‘ only in casing.

一、报错信息 VSCode报错如下&#xff1a; File name ‘d:/object/oral-data-management/src/components/VisitLogPopup/Info.vue’ differs from already included file name ‘d:/object/oral-data-management/src/components/VisitLogPopup/INfo.vue’ only in casing. The…

【PostgreSQL17新特性之-事务级别超时参数transaction_timeout】

PostgreSQL数据库里有多个和会话相关的参数&#xff0c;PostgreSQL17-beta1版本新增了一个transaction_timeout参数&#xff0c;来限制事务的持续时间。 当前的一些和会话相关的超时参数如下 -----------------------------------------------------------------------------…

第一讲:单片机STC89C52+RA8889驱动控制彩屏(源码公开)

51单片机驱动控制彩屏系列讲座 第一讲&#xff1a;单片机STC89C52RA8889驱动控制彩屏&#xff08;源码公开&#xff09; 单片机通过SPI与RA8889进行通信&#xff0c;由于单片机是5V&#xff0c;RA8889是3.3V,故需要进行电平转换&#xff0c;有现成的模组TXS0108E等可以采用。…

linux开放某一个端口具体步骤

场景&#xff1a;当服务器防火墙不能直接关闭&#xff0c;但是客户端必须要访问服务器某一个端口时。 处理&#xff1a;对服务器端进行处理&#xff0c;只将该端口开放出来让客户端访问 本地使用vm安装了一个centos服务器&#xff0c;ip地址是 192.168.200.130。在这里充当服…

Kafka原生API使用Java代码-生产者-异步发送消息回调

文章目录 1、异步发送消息&回调1.1、pom.xml1.2、KafkaProducer1.java 1、异步发送消息&回调 回调就是接收kafka的响应 1.1、pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0&q…

Android Studio无法改变Button背景颜色解决办法

大家好&#xff0c;我是咕噜铁蛋&#xff01;今天我来和大家探讨一个在Android开发中常见但可能让初学者感到困惑的问题——如何在Android Studio中改变Button的背景颜色。这个问题看似简单&#xff0c;但实际操作中可能会遇到一些意想不到的挑战。接下来&#xff0c;我将从多个…

Facebook:社交世界的接口

在当今数字时代&#xff0c;社交媒体已经成为了人们生活中不可或缺的一部分&#xff0c;而Facebook作为其中的巨头之一&#xff0c;扮演着至关重要的角色。本文将带您深入探索Facebook这张社交世界的画卷&#xff0c;全面了解这个令人着迷的平台。 起源与历程 Facebook的故事始…

AI虚拟试穿革命:I2VEdit技术引领电商视频内容创新

在当今快速迭代的电子商务领域,用户体验与内容创新是企业竞争力的核心要素。随着AI技术的飞速进步,AI虚拟试穿已不再局限于静态图像,而是迈向了动态视频的新纪元。本文将深入解析一项革新性技术——I2VEdit,如何以其独到之处,为电商尤其是服装零售行业带来一场内容创作与产…

JVM学习-字节码指令集(二)

对象的创建与访问指令 创建指令 虽然类实例和数组都是对象&#xff0c;但Java虚拟机对类实例和数组的创建和操作使用了不同的字节码指令创建类实例指令&#xff1a;new 它接收一个操作数&#xff0c;指向常量池的索引&#xff0c;表示要创建的类型&#xff0c;执行完成后&am…

重磅发布,2024精选《制造业商业智能BI最佳实践合集 》

在数字时代&#xff0c;中国制造业正面临着前所未有的深刻变革。 商业环境的复杂性与多变性、全球化竞争的激烈程度、消费需求的快速演变&#xff0c;以及新技术的持续进步等多种因素共同推动着制造企业积极加入数字化转型的潮流。 在这个转型的过程中&#xff0c;转型的速度…

Java设计模式 _行为型模式_备忘录模式

一、备忘录模式 1、备忘录模式 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型模式。通过保存一个对象的某个状态&#xff0c;以便在适当的时候恢复对象。 2、实现思路 &#xff08;1&#xff09;、定义记录数据的格式规范。 &#xff08;2&#xff09;、编…

pyqt Qtreeview分层控件

pyqt Qtreeview分层控件 介绍效果代码 介绍 QTreeView 是 PyQt中的一个控件&#xff0c;它用于展示分层数据&#xff0c;如目录结构、文件系统等。QTreeView 通常与模型&#xff08;如 QStandardItemModel、QFileSystemModel 或自定义模型&#xff09;一起使用&#xff0c;以管…

WAF几种代理模式详解

WAF简介 WAF的具体作用就是检测web应用中特定的应用&#xff0c;针对web应用的漏洞进行安全防护&#xff0c;阻止如SQL注入&#xff0c;XSS&#xff0c;跨脚本网站攻击等 正向代理 WAF和客户端与网络资源服务器都建立连接&#xff0c;但是WAF 的工作口具有自己的 IP 地址&…

oracle数据回显时候递归实战

太简单的两篇递归循环 orcale 在项目里递归循环实战 先看资产表T_ATOM_ASSET结构 看业务类别表T_ATOM_BUSI_CATEGORY结构 问题出现 页面显示 实际对应的归属业务分类 涉及到oracle递归实战(这里不会如何直接在atomAsset的seelct里面处理递归回显) 直接在实现层看atomAs…

zynq/zynqMP启动模式总结:FLASH+emmc启动/petalinux烧写速度最快的启动方式

因客户要求zynq开发板只有FLASH和emmc&#xff0c;然而还得在petalinux进行开发系统&#xff0c;因FLASH大小有限&#xff0c;所以没办法把内核和根文件地址全部存储到FLASH中&#xff0c;于是想配合emmc进行启动&#xff0c;但是在网上搜索的大多都是只把根文件系统放到了emmc…