本文内容以及后面的几篇文章都是在为 errno 的实现做铺垫,如果没有这些基础知识,你直接跳过去看原理可能会懵圈。
1. 问题提出
有时候,在执行多线程程序的时候,可能需要对某些共享资源进行初始化,一般来说,我们有两种方式:
- 在创建线程之前进行初始化
- 在线程函数中做初始化
对于第一种方式,肯定是最常用的了,不过有时候我们并不想这么做。因为一开始可能并不需要多线程程序,所以希望我们将多线程所使用的共享资源初始化动作推迟到线程函数中。如此将会导致一个问题,每个线程都执行初始化,岂不是被初始化很多次?
它的代码看起来像下面这样:
int *foo;
void thread_init() {
foo = (int*) malloc(sizeof(int));
...
}
void *th_fn1(void *arg) {
thread_init();
...
}
void *th_fn2(void *arg) {
thread_init();
...
}
如果直接这样做的话, foo 指针就会被多次分配内存,导致错误。有没有一种办法,让 thread_init 只执行一次?根据前面我们学过的线程同步内容,我们可以采用标记加线程互斥来解决,不过,这太麻烦!
Linux 已经为我们准备了一套解决方案 —— pthread once.
2. pthread once
在这一套技术方案中,有两个基本要素:
- 提供一个 pthread_once_t 类型全局对象,将其初始化为
PTHREAD_ONCE_INIT
- 提供一个初始化函数,也就是前面的 thread_init 函数
接下来,只需要在每个线程函数中调用它所提供的初始化接口就行了。该初始化接口函数定义如下:
int phtread_once(pthread_once_t *initflag, void (*initfn)(void));
它的两个参数,就是上面讲到的两个基本要素。
接下来,我们用实验来讲清楚它的用法。
3. 程序清单
在 once 程序中,我们只证明,thread_init 函数只会被执行一次。
3.1 代码
// once.c
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
pthread_once_t init_done = PTHREAD_ONCE_INIT;
// 只被执行一次的函数
void thread_init() {
puts("I'm thread init");
}
void* fun(void *arg) {
pthread_once(&init_done, thread_init);
printf("Hello, I'm %s\n", (char*)arg);
return NULL;
}
int main() {
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, fun, "allen");
pthread_create(&tid2, NULL, fun, "luffy");
pthread_create(&tid3, NULL, fun, "zoro");
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
return 0;
}
3.2 编译和运行
- 编译和运行
$ gcc once.c -o once -lpthread
$ ./once
- 运行结果
图1 thread_init 初始化函数只执行了一次
4. 总结
- 知道 pthread once 是干嘛的
- 掌握 pthread once