Linux C/C++编程的线程创建

【图书推荐】《Linux C与C++一线开发实践(第2版)》_linux c与c++一线开发实践pdf-CSDN博客
《Linux C与C++一线开发实践(第2版)(Linux技术丛书)》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

在POSIX API中,创建线程的函数是pthread_create,该函数声明如下:

int pthread_create(pthread_t *pid, const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);

其中,参数pid是一个指针,指向创建成功后的线程的ID,pthread_t其实就是unsigned long int;attr是指向线程属性结构pthread_attr_t的指针,如果为NULL,则使用默认属性;start_routine指向线程函数的地址,线程函数就是线程创建后要执行的函数;arg指向传给线程函数的参数,如果执行成功,函数返回0。

创建完子线程后,主线程会继续执行后面的代码,这就可能会出现创建的子线程还没执行完,主线程就结束了,比如控制台程序,而主线程结束就意味着进程结束了。在这种情况下,我们需要等待子线程全部运行结束后再继续执行主线程。还有一种情况,主线程为了统计各个子线程工作的结果而需要等待子线程结束后再继续执行。POSIX提供了函数pthread_join来等待子线程结束,即子线程的线程函数执行完毕后,pthread_join才返回,因此pthread_join是一个阻塞函数。函数pthread_join会让主线程挂起(休眠,就是让出CPU),直到子线程都退出,同时pthread_join能让子线程所占资源得到释放。子线程退出后,主线程会接收到系统的信号,从休眠中恢复。函数pthread_join声明如下:

int pthread_join(pthread_t pid, void **value_ptr);

其中,参数pid是所等待线程的ID;value_ptr通常可设为NULL,如果不为NULL,则pthread_join 复制一份线程退出值到一个内存区域,并让*value_ptr指向该内存区域,因此pthread_join还有一个重要功能就是能获得子线程的返回值(这一点后面会讲到)。如果函数执行成功就返回0,否则返回错误码。

下面来实践一下,看几个简单的例子。

【例8.1】创建一个简单的线程,不传参数

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h> 			// 休眠

void *thfunc(void *arg) 		// 线程函数
{
	printf("in thfunc\n");
	return (void *)0;   
}
int main(int argc, char *argv [])
 {
	pthread_t tidp;
	int ret;

	ret = pthread_create(&tidp, NULL, thfunc, NULL); // 创建线程
	if (ret)
	{
		printf("pthread_create failed:%d\n", ret);
		return -1;
	}
	 
	sleep(1); // main线程挂起1秒钟,为了让子线程有机会执行
	printf("in main:thread is created\n");
	 
	return 0;
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

# g++ -o test test.cpp -lpthread
# ./test
in thfunc
in main:thread is created
#

在这个例子中,首先创建一个线程,在线程函数中打印一行字符串后结束;而主线程在创建子线程后,会等待1秒,这样不至于因为主线程过早结束而导致进程结束。如果没有等待函数sleep,则可能子线程的线程函数还没来得及执行,主线程就结束了,这样会导致子线程的线程函数没有机会执行,因为主线程已经结束,整个应用程序已经退出了。
【例8.2】创建一个线程,并传入整型参数
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

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

void *thfunc(void *arg)
{
    int *pn = (int*)(arg); // 获取参数的地址
    int n = *pn; 
    printf("in thfunc:n=%d\n", n);
    return (void *)0; 
}
int main(int argc, char *argv [])
{
    pthread_t tidp;
    int ret, n=110;

    ret = pthread_create(&tidp, NULL, thfunc, &n);    // 创建线程并传递n的地址
    if (ret)
    {
        printf("pthread_create failed:%d\n", ret);
        return -1;
    }
    pthread_join(tidp,NULL);                         // 等待子线程结束
    printf("in main:thread is created\n");
     
    return 0;
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

# g++ -o test test.cpp -lpthread
# ./test
in thfunc:n=110
in main:thread is created
#

这个例子和上面的例子有两点不同:一是创建线程的时候,把一个整型变量的地址作为参数传给线程函数;二是等待子线程结束没有用sleep函数,而是用pthread_join函数。sleep只是等待一个固定的时间,有可能在这个固定的时间内子线程早已经结束,或者子线程运行的时间大于这个固定时间,因此用它来等待子线程结束并不精确,而用pthread_join函数则会一直等到子线程结束后才执行该函数后面的代码,它的第一个参数就是子线程的ID。

【例8.3】创建一个线程,并传递字符串作为参数
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

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

void *thfunc(void *arg)
{
    char *str;
    str = (char *)arg;                     // 得到传进来的字符串
    printf("in thfunc:str=%s\n", str);    // 打印字符串
    return (void *)0;
}
int main(int argc, char *argv [])
{
    pthread_t tidp;
    int ret;
    const char *str = "hello world";
    ret = pthread_create(&tidp, NULL, thfunc, (void *)str);// 创建线程并传递str 
    if (ret)
    {
        printf("pthread_create failed:%d\n", ret);
        return -1;
    }
    pthread_join(tidp, NULL); // 等待子线程结束
    printf("in main:thread is created\n");
     
    return 0;
}


(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

# g++ -o test test.cpp -lpthread
# ./test
in thfunc:n=110,str=hello world
in main:thread is created
#

【例8.4】创建一个线程,并传递结构体作为参数
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

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

typedef struct  // 定义结构体的类型
{
    int n;
    char *str;
}MYSTRUCT;
void *thfunc(void *arg)
{
    MYSTRUCT *p = (MYSTRUCT*)arg;
    printf("in thfunc:n=%d,str=%s\n", p->n,p->str); // 打印结构体的内容
    return (void *)0;
}
int main(int argc, char *argv [])
{
    pthread_t tidp;
    int ret;
    MYSTRUCT mystruct; // 定义结构体
    // 初始化结构体
    mystruct.n = 110;
    mystruct.str = "hello world";
    ret = pthread_create(&tidp, NULL, thfunc, (void *)&mystruct);
    // 创建线程并传递结构体地址 
    if (ret)
    {
        printf("pthread_create failed:%d\n", ret);
        return -1;
    }
    pthread_join(tidp, NULL); // 等待子线程结束
    printf("in main:thread is created\n");
     
    return 0;
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

-bash-4.2# g++ -o test test.cpp -lpthread
-bash-4.2# ./test
in thfunc:n=110,str=hello world
in main:thread is created
-bash-4.2#

【例8.5】创建一个线程,共享进程数据
(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

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

int gn = 10;                         // 定义一个全局变量,将会在主线程和子线程中用到
void *thfunc(void *arg)
{
    gn++;                                // 递增1
    printf("in thfunc:gn=%d,\n", gn);     // 打印全局变量gn值
    return (void *)0;
}

int main(int argc, char *argv [])
{
    pthread_t tidp;
    int ret;
    ret = pthread_create(&tidp, NULL, thfunc, NULL);
    if (ret)
    {
        printf("pthread_create failed:%d\n", ret);
        return -1;
    }
    pthread_join(tidp, NULL);             // 等待子线程结束
    gn++;                                 // 子线程结束后,gn再递增1
    printf("in main:gn=%d\n", gn);        // 再次打印全局变量gn值
     
    return 0;
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

-bash-4.2# g++ -o test test.cpp -lpthread
-bash-4.2# ./test
in thfunc:gn=11,
in main:gn=12
-bash-4.2#

从上例中可以看到,全局变量gn首先在子线程中递增1,子线程结束后,再在主线程中递增1。两个线程都对同一个全局变量进行了访问。

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

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

相关文章

Gitee上获取renren-fast-vue install并run dev错误处理

目的&#xff1a;获取一个手脚架、越简约越好、越干净越好、于是看上了renren-fast-vue… 前端&#xff1a;vue2 后端&#xff1a;jdk1.8 mysql 5.7 SpringBoot单体架构 一开始只是下载前后端项目到本地&#xff0c;一堆乱七八糟的错误&#xff0c;网上找的资料也参差不齐… …

线程和进程(juc)

线程 一&#xff1a;概念辨析 1&#xff1a;线程与进程 进程&#xff1a; 1&#xff1a;程序由指令和数据组成&#xff0c;指令要执行&#xff0c;数据要读写&#xff0c;就需要将指令加载给cpu&#xff0c;把数据加载到内存&#xff0c;同时程序运行时还会使用磁盘&#x…

五、docker的网络模式

五、docker的网络模式 5.1 Docker的四种网络模式 当你安装docker时&#xff0c;它会自动创建三个网络&#xff0c;可使用如下命令查看&#xff1a; [rootlocalhost ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 7390284b02d6 bridge bridge lo…

【AWS re:Invent 2024】一文了解EKS新功能:Amazon EKS Auto Mode

文章目录 一、为什么要使用 Amazon EKS Auto Mode&#xff1f;二、Amazon EKS自动模式特性2.1 持续优化计算成本2.2 迁移集群操作2.3 EKS 自动模式的高级功能 三、EKS Auto 集群快速创建集群配置四、查看来自 API 服务器的指标五、EKS 相关角色权限设置六、参考链接 一、为什么…

数据结构——有序二叉树的删除

在上一篇博客中&#xff0c;我们介绍了有序二叉树的构建、遍历、查找。 数据结构——有序二叉树的构建&遍历&查找-CSDN博客文章浏览阅读707次&#xff0c;点赞18次&#xff0c;收藏6次。因为数据的类型决定数据在内存中的存储形式。left right示意为左右节点其类型也为…

git pull error: cannot lock ref

Git: cannot lock ref ‘refs/remotes/origin/feature/xxx’: refs/remotes/origin/feature/xxx/car’ exists; cannot create refs/remotes/origin/feature/xxx git remote prune origin重新整理服务端和本地的关联关系即可

树与图深度优先遍历——acwing

题目一&#xff1a;树的重心 846. 树的重心 - AcWing题库 分析 采用暴力枚举&#xff0c;试探每个点&#xff0c;除去之后&#xff0c;连通分量最大值是多少&#xff0c; 各个点的最大值找最小的 因为可以通过 dfs 来得到 根u以下点数&#xff0c;以及可以求各分树的点数&am…

ultralytics-YOLOv11的目标检测解析

1. Python的调用 from ultralytics import YOLO import os def detect_predict():model YOLO(../weights/yolo11n.pt)print(model)results model(../ultralytics/assets/bus.jpg)if not os.path.exists(results[0].save_dir):os.makedirs(results[0].save_dir)for result in…

图形开发基础之在WinForms中使用OpenTK.GLControl进行图形绘制

前言 GLControl 是 OpenTK 库中一个重要的控件&#xff0c;专门用于在 Windows Forms 应用程序中集成 OpenGL 图形渲染。通过 GLControl&#xff0c;可以轻松地将 OpenGL 的高性能图形绘制功能嵌入到传统的桌面应用程序中。 1. GLControl 的核心功能 OpenGL 渲染上下文&…

Facebook广告文案流量秘诀

Facebook 广告文案是制作有效 Facebook 广告的关键方面。它侧重于伴随广告视觉元素的文本内容。今天我们的博客将深入探讨成功的 Facebook 广告文案的秘密&#xff01; 一、广告文案怎么写&#xff1f; 正文&#xff1a;这是帖子的正文&#xff0c;出现在您姓名的正下方。它可…

java面向对象实验——扫雷+24点

扫雷 窗口绘制&#xff1a; GameWin package com.sxt;import javax.swing.*;public class GameWin extends JFrame {void launch(){this.setVisible(true);this.setSize(500, 500);this.setLocationRelativeTo(null);this.setTitle("SWE23070扫雷游戏");this.setD…

Ubuntu24安装 python3-mysql.connector

正确命令 sudo apt install python3-mysql.connector说明 网络上已有的文章Python版本和Ubuntu版本旧&#xff0c;命令不生效。

【西门子PLC.博途】——在S71200里写时间设置和读取功能块

之前我们在这篇文章中介绍过如何读取PLC的系统时间。我们来看看在西门子1200里面有什么区别。同时也欢迎关注gzh。 我们在S71200的帮助文档中搜索时间后找到这个数据类型 在博途中他是一个结构体&#xff0c;具体为 然后我们再看看它带的读取和写入时间块 读取时间&#xff1…

如何搭建智慧工厂?IOT+AI:赋能未来制造业灯塔工厂建设

在当今数字化和智能化的浪潮中&#xff0c;传统制造业正经历着前所未有的变革。智慧工厂作为智能制造的核心内容&#xff0c;正逐步成为未来制造业的发展趋势。本文将深入探讨智慧工厂的搭建过程&#xff0c;以及IoT&#xff08;物联网&#xff09;和AI&#xff08;人工智能&am…

内存图及其画法

所有的文件都存在硬盘上&#xff0c;首次使用的时候才会进入内存 进程&#xff1a;有自己的Main方法&#xff0c;并且依赖自己Main运行起来的程序。独占一块内存区域&#xff0c;互不干扰。内存中有一个一个的进程。 操作系统只认识c语言。操作系统调度驱动管理硬件&#xff0…

Linux下,用ufw实现端口关闭、流量控制(二)

本文是 网安小白的端口关闭实践 的续篇。 海量报文&#xff0c;一手掌握&#xff0c;你值得拥有&#xff0c;让我们开始吧&#xff5e; ufw 与 iptables的关系 理论介绍&#xff1a; ufw&#xff08;Uncomplicated Firewall&#xff09;是一个基于iptables的前端工具&#xf…

Python使用Selenium自动实现表单填写之蛇年纪念币蛇钞预约(附源码,源码有注释解析,已测试可用

Python实现纪念币预约自动填写表单 声明:本文只做技术交流,不可用代码为商业用途,文末有源码下载,已测试可用。 Part 1 配置文件改写(源码 有详细的注释说明 读取配置文件,自己组数据库,录入信息 配置文件 Part 2 主函数 每一期的xpath路径都不一样 所以需要提前去网站…

内存管理面试常问

为什么要有虚拟内存&#xff1f; 虚拟内存 如果你是电⼦相关专业的&#xff0c;肯定在⼤学⾥捣⿎过单⽚机。 单⽚机是没有操作系统的&#xff0c;所以每次写完代码&#xff0c;都需要借助⼯具把程序烧录进去&#xff0c;这样程序才能跑起来。 另外&#xff0c; 单⽚机的 CPU …

插入排序⁻⁻⁻⁻直接插入排序希尔排序

引言 所谓的排序&#xff0c;就是使一串记录按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 常见的排序算法有&#xff1a; 今天我们主要学习插入排序的直接插入排序和希尔排序。 直接插入排序 什么是直接插入排序&#xff1f; 直接插入排序其…

基于Springboot + Vue开发的飞驰驾校预约学习平台(项目源码 + lw)

一、功能介绍 飞驰驾校预约学习平台包含管理员、教练、用户三个角色以及前后台系统。 主要功能 前台系统功能 首页展示、理论考试、教练信息、教练预约、学习资料、学习视频观看、用户留言、公告信息展示、个人中心信息管理 后台系统功能 管理员或用户登录成功后&#xff0c…