目录
threads函数库的简介
介绍
threads函数库是c11标准新加的多线程函数库,使用时,要包含threads.h头文件,编译时规定std=c11
我们都知道,程序运行时会产生进程,程序会被加载到进程的地址空间中。但是,进程还要有执行单位,那就是线程。进程创建时,肯定要有至少一个线程执行代码。进程刚创建时创建的线程叫主线程,主线程创建的线程叫子线程。
glibc在内的大部分标准库都不支持使用这个函数库,不支持这个函数库的编译器会定义__STDC_NO_THREADS__宏。而musl的标准库支持这个函数库。因此,如无特殊说明,本文使用的编译器均是musl-gcc。
musl的安装与使用
musl的安装
在ubuntu中,我们可以采用apt安装这个标准库。在终端中输入:
sudo apt install musl
sudo apt install musl-tools
就可以安装musl标准库了。
我们可以输入:
musl-gcc --version
检测是否安装成功。musl-gcc是采用musl函数库的gcc编译器,如果安装成功,会看到:
gcc (Ubuntu 5.5.0-12ubuntu1~16.04) 5.5.0 20171010
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
musl的使用
使用这个标准库非常简单,只需要输入:
musl-gcc ...
就可以了。使用的方法类似普通gcc的使用。
线程的使用
线程的创建
线程的创建采用thrd_create(3)函数,这个函数的原形如下:
int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
这个函数将会创建一个运行func函数的线程,在thrd_create(3)函数里,arg将会作为func函数的参数。如果线程创建成功,thr指向的对象将被设置为所创建线程的标识符。这个函数完成的同时,所创建的线程将开始。
以下是对其参数的详细解释:
- thr: 指向将要存放线程标识符的地址
- func: 需要执行的函数。注意:这个函数是int (*)(void *)类型的,这意味着函数的返回值是int类型,接受一个void *类型的参数。
- arg: 向执行函数传递的参数
这个函数的返回值是一个枚举,其定义如下:
enum {
thrd_success = /* unspecified */,
thrd_nomem = /* unspecified */,
thrd_timedout = /* unspecified */,
thrd_busy = /* unspecified */,
thrd_error = /* unspecified */
};
其中,thrd_success是线程创建成功的返回值。thrd_nomem表示因地址空间不足造成线程创建失败。thrd_error表示其他错误发生。这个枚举类型的其他枚举形式不会返回。
另外,当线程结束、加入或脱离后,线程标识符可能被重新使用。
当线程返回,或调用thrd_exit(3)时,线程结束。线程的结束码会被设为执行函数的返回值或thrd_exit(3)函数的参数。
线程的加入与脱离
当线程结束、加入或脱离后,线程标识符可能被重新使用。那么,什么是加入、脱离呢?如何操作?
线程的加入
线程的加入意味着阻塞当前的线程直到线程结束。通过thrd_join(3)函数可以实现线程的加入。
int thrd_join( thrd_t thr, int *res );
这个函数与线程同时终止,其参数如下:
- thr:要进行加入操作的线程
- res:当线程结束时,将线程的结束码放入该参数指向的地址中。这个参数可以为NULL。
这个函数的返回值仍然是上文所说的枚举。如果成功,则返回thrd_success,否则返回thrd_error。
线程的脱离
线程的脱离与加入相反,意味着将线程同当前环境脱离出来,当线程结束时,将自动销毁,而不会阻塞线程。通过thrd_detach(3)实现线程脱离。
int thrd_detach( thrd_t thr );
这个函数的参数只有一个,即thr,表示要进行脱离操作的线程。这个函数的返回值仍然是上文所说的枚举。如果成功,则返回thrd_success,否则返回thrd_error。
线程的终止
在以下情况下,线程终止:
- 执行函数调用thrd_exit(3)函数;
- 执行函数返回,这相当于调用thrd_exit(3)函数;
- 本进程中任何线程调用exit(3)函数,或main函数返回,这将会使本进程所有线程终止。
接下来给大家介绍thrd_exit(3)函数,在C11标准中,其原型如下:
_Noreturn void thrd_exit( int res );
在C23标准中,其原型如下:
[[noreturn]] void thrd_exit( int res );
res表示结束码,这将会成为thrd_join(3)的返回值。
exit(3)是标准库中的函数,这里也给大家介绍一下:
void exit(int status);
使用时需要包含stdlib.h头文件。
这个函数非常简单。它会导致进程终止。并且status参数与上0377的结果将会返回给父进程。为什么要要与上一个0377?因为子进程的状态改变不只有终止,还有被信号停止、被信号重新开始。为了使系统及用户区分,因此需要进行位运算。
事实上,exit(0)等价于main函数中return 0.这说明main函数返回时也会与上0377.所以,如果不知道位操作,可以简单的把exit看成main函数的返回。
举个例子
以下程序展示了如何创建一个线程,输出Hello World:
#include <stdio.h>
#ifndef __STDC_NO_THREADS__
#include <threads.h>
#endif
int func(void *args){
printf("Hello World\n");
return 0;
}
int main(void){
#ifdef __STDC_NO_THREADS__
printf("cannot use threads\n");
#else
thrd_t th;
thrd_create(&th, func, NULL);
thrd_join(th, NULL);
#endif
return 0;
}
如果__STDC_NO_THREADS__宏被定义,则这个程序的流程如下:
-
定义函数
func
:- 这个函数打印"Hello World"并返回0。
-
main
函数:- 定义一个线程变量
th
。 - 使用
thrd_create
函数创建一个新线程,执行函数是func
,并且没有传递任何参数。 - 使用
thrd_join
进行线程的加入操作,等待线程完成。
- 定义一个线程变量
-
返回0,结束程序。
这个程序对__STDC_NO_THREADS__宏进行了多次检测,如果没有这个宏,就不能使用threads库。这样的写法是为了更好的兼容跨平台程序。
我们使用musl编译:
musl-gcc -std=c11 th1.c
./a.out
输出:
Hello World
可以看到程序成功输出了Hello World。使用threads库可以更好的支持跨平台应用。
如果我们把程序的thrd_join改成thrd_detach:
#include <stdio.h>
#ifndef __STDC_NO_THREADS__
#include <threads.h>
#endif
int func(void *args){
printf("Hello World\n");
return 0;
}
int main(void){
#ifdef __STDC_NO_THREADS__
printf("cannot use threads\n");
#else
thrd_t th;
thrd_create(&th, func, NULL);
thrd_detach(th);
#endif
return 0;
}
则不会有输出。这是因为线程创建后main函数就返回了,线程终止。
互斥体
互斥体又叫互斥锁,可以解决多个线程同时访问一块地址的问题。先看一段代码:
#include <threads.h>
#include <stdio.h>
int test = 10;
int func(void *args){
while(--test > 0){
printf("%d\n", test);
}
retu