pthread多线程应用介绍

前言

一个进程,想要并行处理不同的事情,就可以使用多线程来实现。

比如有两个led灯,一个红色led、一个蓝色led。 想让红色led以2秒周期闪烁, 蓝色led以1秒周期闪烁。

在一个进程里面,当然可以通过一些技巧来实现一个while(1)实现两个led不同周期闪烁。

但我们可以使用两个线程来实现,一个线程控制红色led,一个线程控制蓝色led。

main
线程1,红色led 2秒周期闪烁
线程2,蓝色led 1秒周期闪烁
结束

多线程的概念与实时操作系统,如freertos、ucos-ii的多任务类似。


pthread API相关介绍

pthread_create()

可以通过pthread_create函数切入多线程的使用,该函数用于创建一个线程

linux系统下,通过man手册,可以查询对应的使用方法。

man pthread_create # 终端输入man pthread_create 查询man 手册

PTHREAD_CREATE(3)                                                                             Linux Programmer's Manual                                                                            PTHREAD_CREATE(3)

NAME
       pthread_create - create a new thread

SYNOPSIS
       #include <pthread.h>

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

       Compile and link with -pthread.
 # 省略部分描述

函数原型

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

从函数原型中,可以看到,创建一个线程需要准备4个参数。

pthread_t *thread
const pthread_attr_t *attr
void *(*start_routine) (void *)
void *arg

1、pthread_t *thread --> 线程句柄 or 编号

相当于一个线程句柄, 线程编号。实际上是一个无符号长整型

typedef unsigned long int pthread_t;

定义变量时,可以一个个线程的变量进行定义,比如

pthread_t handle1;
pthread_t handle2;

也可以直接通过一个数组来定义多个句柄

#define MAX_HANDLE	1024
int handle_num = 0; //记录已经创建多少个句柄
pthread_t handle_list[MAX_HANDLE]; //创建1024个句柄,可以通过handle_list[handle_num]来创建线程。

线程编号(句柄)在一些线程操作中可以使用。
比如在int pthread_join(pthread_t thread, void **retval);中可以使用。
pthread_join()用来等待一个线程的结束。

2、const pthread_attr_t *attr --> 线程属性设置

对于简单利用的线程来说, 一般都无需设置attr,使用默认数值即可, 即将attr设置NULL。

man手册对于attr的描述

	The  attr  argument points to a pthread_attr_t structure whose contents are used at thread
creation time to determine attributes for the new thread; 
	this structure is initialized using pthread_attr_init(3) and related functions.  
	If attr is NULL, then the thread is created with default attributes

可以通过attr设置一个新线程的属性, pthread_attr_t 结构体如下:

typedef struct
{
       int                       detachstate;     线程的分离状态
       int                       schedpolicy;     线程调度策略
       struct sched_param        schedparam;      线程的调度参数
       int                       inheritsched;    线程的继承性
       int                       scope;           线程的作用域
       size_t                    guardsize;       线程栈末尾的警戒缓冲区大小
       int                       stackaddr_set;
       void *                    stackaddr;       线程栈的位置
       size_t                    stacksize;       线程栈的大小
}pthread_attr_t;

man手册提到,默认状态下,不指定attr,即attr为NULL,创建的线程是joinable

By default, a new thread is created in a joinable state, unless attr was set to create the thread in a detached state (using pthread_attr_setdetachstate(3))

线程默认栈大小,会与不同的架构有关系,可以通过pthread_attr_setstacksize()函数来设置

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
stacksize的单位是bytes

不同架构默认栈大小表格如下:

架构默认栈大小
i3862 MB
IA-6432 MB
PowerPC4 MB
S/3902 MB
Sparc-322 MB
Sparc-644 MB
x86_642 MB

对于ubuntu20.04来说,可以用uname -a查看对于架构,可以看到是x86_64,所以对于的默认栈大小是2MB

uname -a

Linux allen 5.13.0-41-generic #46~20.04.1-Ubuntu SMP Wed Apr 20 13:16:21 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

3、void *(*start_routine) (void *) --> 回调函数

void *(*start_routine) (void *)对应的就是实际多线程运行的函数。
是一个形参为void *,返回为void *类型的函数
如:

void * led1_task(void *args)

void *args形参用于线程创建时的传参。在下面会说到

4、void *arg --> 线程传入参数

线程传入参数,是void *形式,没有限定参数传入的类型。既可以是int、char等, 也可以传入一个结构体,最终会在回调函数的形参void *arg中重新提取。

struct args {
	char name[10];
	int fd;
};

无论变量类型是什么,传入的都是参数的地址。

pthread_join()

int pthread_join(pthread_t thread, void **retval);

用于等待一个线程结束,并获取其返回值。

比如创建了两个线程,pthread1、pthread2
在线程2中可以等待线程1结束,在运行。

void **retval;
ret = pthread_join(thread_id2, retval);

pthread_exit()

pthread_exit()用于线程任务退出。当线程任务运行完成,需要调用该函数将线程退出。

void pthread_exit(void *retval);

可以看到线程退出的时候,可以返回一个void *类型数值retval。 同样也是返回的一个地址。
比如可以返回一个字符串

void * pthread_task(void *args)
{
	pthread_exit("task finish");
}

结合pthread_join()函数使用,等待pthread_task结束后, 调用pthread_join()的程序,就可以获取到字符串task finish


代码实现

创建测试代码文件,命名为test.c,将编写一个带有两个线程的代码, 分别测试整型与结构体传参。

#include <stdio.h>
#include <pthread.h> //引入pthread头文件

#define 	MAX_THREAD		1024

int thread_num = 0; //定义变量,记录当前有多少个线程。

pthread_t thread_list[MAX_THREAD]; //定义线程句柄数组,最多可以用于创建MAX_THREAD个线程

struct args_t { //自定义一个参数结构体
	char name[10];
	int number;
};


static void * thread_task1(void *args) // 线程1,用于测试整型传参
{
	int number = *(int *)args; // 提取整型传参
	
	printf("%s: get number = %d\n", __func__, number);
	
	pthread_exit(0); //退出线程
}

static void *thread_task2(void *args) // 线程2,用于测试结构体传参
{
	struct args_t arg = *(struct args_t *)args; //提取结构体传参

	printf("%s: arg.name = %s\n, arg.number = %d\n", __func__, arg.name, arg.number);
	
	pthread_exit(0); //退出线程
}

static void thread_create_task1(void){
	int number = 100;
	int ret = 0;
	ret = pthread_create(	&thread_list[thread_num], //设置线程句柄存储位置
							NULL,	// attr使用默认值
							(void *)&thread_task1,  //线程1回调函数
							(void *)&number); // 传入整型参数number

	if(ret != 0){
		printf("%s create failed!\n", __func__);
		return ;
	}

	thread_num++; //线程创建成功,记录加1.
}

static void thread_create_task2(void){
	struct args_t arg = {
		.name = "pthread",
		.number = 1000
	};
	
	int ret = 0;
	ret = pthread_create(	&thread_list[thread_num], //设置线程句柄存储位置
							NULL,	// attr使用默认值
							(void *)&thread_task2,  //线程2回调函数
							(void *)&arg); // 传入结构体参数arg

	if(ret != 0){
		printf("%s create failed!\n", __func__);
		return ;
	}

	thread_num++; //线程创建成功,记录加1.
}

int main(int argc, char **argv)
{
	thread_num = 0;
	thread_create_task1();
	thread_create_task2();

	while(1);

	return 0;
}

代码编译

使用man查看pthread相关的函数时,会提醒我们编译需要加上-l pthread,即需要链接pthread库编译。

SYNOPSIS
       #include <pthread.h>

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

       Compile and link with -pthread. # 提醒需要添加链接编译

所以编译上述代码的命令如下:

gcc test.c -o a.out -l pthread

运行a.out测试结果

./a.out

thread_task1: get number = 100
thread_task2: arg.name = pthread, arg.number = 1000

可以看到两个线程都成功创建,并获取到了对应的参数。


总结

  1. 可以通过man手册来查询pthread_create,进而切入线程创建过程。

  2. 多线程的简单使用,需要准备4个参数,线程编号, 线程属性attr,线程回调函数,线程参数,一般attr参数可以设置为NULL。

  3. 理解线程传参是如何进行的,实际上传入的是一个(void *)类型的地址值,所以可以传递int变量、结构体,甚至是其他更复杂的变量。

  4. 实际开发过程中,出现过局部变量作为传入参数,会出现线程获取不到的问题,但后续代码测试正常。之前猜测原因是局部变量销毁时,线程任务还没开始获取传入参数,导致再去获取局部变量的地址,内容已经改变。保留在这里!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值