嵌入式Linux-线程创建与终止

1. 线程的创建

1.1 创建线程

启动程序时,创建的进程只是一个单线程的进程,称之为初始线程或主线程,本小节我们讨论如何创建一个新的线程。

创建线程与创建进程的方法是一样的,让我们来看一下创建线程的函数:

#include <pthread.h>

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

我们来解读一下参数的含义:

参数含义
threadpthread_t类型指针,当pthread_create()成功返回时,新创建的线程的线程ID会保存在参数thread
attrpthread_attr_t类型指针,指向pthread_attr_t类型的缓冲区,pthread_attr_t数据类型定义了线程的各种属性,如果将参数attr设置为NULL,那么表示将线程的所有属性设置为默认值,以此创建新线程。
start_routine参数start_routine是一个函数指针,指向一个函数,新创建的线程从start_routine()函数开始运行,该函数返回值类型为void*,并且该函数的参数只有一个void*,其实这个参数就是pthread_create()函数的第四个参数arg
arg传递给start_routine()函数的参数。一般情况下,需要将arg指向一个全局或堆变量,意思就是说在线程的生命周期中,该arg指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。当然也可将参数arg设置为NULL,表示不需要传入参数给start_routine()函数。
返回值成功返回0;失败时将返回一个错误号,并且参数thread指向的内容是不确定的。

注意pthread_create()在调用失败时通常会返回错误码,它并不像其它库函数或系统调用一样设置errno,每个线程都提供了全局变量errno的副本,这只是为了与使用errno到的函数进行兼容,在线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局变量,这样可以把错误的范围限制在引起出错的函数中。

线程创建成功,新线程就会加入到系统调度队列中,获取到CPU之后就会立马从start_routine()函数开始运行该线程的任务;调用pthread_create()函数后,通常我们无法确定系统接着会调度哪一个线程来使用CPU资源,先调度主线程还是新创建的线程呢(而在多核CPU或多CPU系统中,多核线程可能会在不同的核心上同时执行)? 如果程序对执行顺序有强制要求,那么就必须采用一些同步技术来实现。这与前面学习父、子进程时也出现了这个问题,无法确定父进程、子进程谁先被系统调度。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
	 printf("新线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
	 return (void *)0;
}

int main(void)
{
	 pthread_t tid;
	 int ret;
	 ret = pthread_create(&tid, NULL, new_thread_start, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "Error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 printf("主线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
	 sleep(1);
	 exit(0);
}

应该将pthread_t作为一种不透明的数据类型加以对待,但是在示例代码中需要打印线程ID,所以要明确其数据类型,示例代码中使用了printf()函数打印线程ID时,将其作为unsignedlongint数据类型,在Linux 系统下,确实是使用unsignedlongint来表示pthread_t,所以这样做没有问题!
主线程休眠了1秒钟,原因在于,如果主线程不进行休眠,它就可能会立马退出,这样可能会导致新创建的线程还没有机会运行,整个进程就结束了。
在主线程和新线程中,分别通过getpid()和pthread_self()来获取进程ID和线程ID,将结果打印出来,运行结果如下所示:
在这里插入图片描述
编译时出现了错误,提示“对‘pthread_create’未定义的引用”,示例代码确实已经包含了<pthread.h>头文件,但为什么会出现这样的报错,仔细看,这个报错是出现在程序代码链接时、而并非是编译过程,所以可知这是链接库的文件,如何解决呢?
在这里插入图片描述
从打印信息可知,正如前面所介绍那样,两个线程的进程ID相同,说明新创建的线程与主线程本来就属于同一个进程,但是它们的线程ID不同。从打印结果可知,Linux系统下线程ID数值非常大,看起来像是一个指针。

2. 线程ID

2.1 不可或缺的线程ID

就像每个进程都有一个进程ID一样,每个线程也有其对应的标识,称为线程ID。进程ID在整个系统中是唯一的,但线程ID不同,线程ID只有在它所属的进程上下文中才有意义。

进程ID使用pid_t数据类型来表示,它是一个非负整数。而线程ID使用pthread_t数据类型来表示,一个线程可通过库函数pthread_self()来获取自己的线程ID,其函数原型如下所示:

#include <pthread.h>

pthread_t pthread_self(void);

3. 终止线程

3.1 如何终止线程

在新线程的启动函数(线程 start 函数)new_thread_start()通过 return 返回之后,意味着该线程已经终止了,除了在线程 start 函数中执行 return 语句终止线程外,终止线程的方式还有多种,可以通过如下方式终止线程的运行:

  1. 线程的 start 函数执行 return 语句并返回指定值,返回值就是线程的退出码;
  2. 线程调用 pthread_exit()函数;
  3. 调用 pthread_cancel()取消线程

tips:这里万分注意的是,慎在线程中使用exit()、_exit()或者_Exit(),否则,你使用的整个程序都会退出,后果看个人的使用场景吧!

我们来看一下pthread_exit()函数将终止调用它的线程,其函数原型如下所示:

#include <pthread.h>

void pthread_exit(void *retval);

参数 retval 的数据类型为 void *,指定了线程的返回值、也就是线程的退出码,该返回值可由另一个线程通过调用 pthread_join()来获取;同理,如果线程是在 start 函数中执行 return 语句终止,那么 return 的返回值也是可以通过 pthread_join()来获取的。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void *new_thread_start(void *arg)
{
	 printf("新线程 start\n");
	 sleep(1);
	 printf("新线程 end\n");
	 pthread_exit(NULL);
}

int main(void)
{
	 pthread_t tid;
	 int ret;
	 ret = pthread_create(&tid, NULL, new_thread_start, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "Error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 printf("主线程 end\n");
	 pthread_exit(NULL);
	 exit(0);
}

在这里插入图片描述
正如上面介绍到,主线程调用 pthread_exit()终止之后,整个进程并没有结束,而新线程还在继续运行。所以进程里的那几个退出函数,大家一定要注意使用呀,可能有时候程序莫名其妙退出,但是检查半天,又能编译成功,但是就是运行不完整程序,会花费大量的时间!

2023年了,祝大家新年快乐!
本文参考正点原子的嵌入式LinuxC应用编程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

The endeavor

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值