与线程相关的就是并发
一、线程初识
-
线程是参与系统调度的最小单位。它被包含在进程之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流(或者说是执行路线、执行流),一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务。 -
当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main Thread),因为它是程序一开始时就运行的线程。应用程序都是以 main()做为入口开始运行的,所以main()函数就是主线程的入口函数,main()函数所执行的任务就是主线程需要执行的任务 -
任何一个进程都包含一个主线程,只有主线程的进程称为单线程进程 -
既然有单线程进程,那自然就存在多线程进程,所谓多线程指的是除了主线程以外,还包含其它的线程,其它线程通常由主线程来创建(调用pthread_create创建一个新的线程),那么创建的新线程就是主线程的子线程 -
主线程的作用
- 其它新的线程(也就是子线程)是由主线程创建的
- 主线程通常会在最后结束运行,执行各种清理工作,譬如回收各个子线程
-
线程的特点
- 线程不单独存在、而是包含在进程中;
- 线程是参与系统调度的基本单位
- 可并发执行。同一进程的多个线程之间可并发执行,在宏观上实现同时运行的效果
- 共享进程资源。同一进程中的各个线程,可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量等等
-
线程与进程的区别 多线程通常对于一些中小型应用程序,而多进程通常会用在一些大型应用程序项目中
- 进程间切换开销大
- 进程间通信较为麻烦,。每个进程都在各自的地址空间中、相互独立、隔离,处在于不同的地址空间中,因此相互通信较为麻烦,
- 同一进程的多个线程间切换开销比较小
- 同一进程的多个线程间通信容易
- 线程创建的速度远大于进程创建的速度
- 多线程在多核处理器上更有优势
- 在实际的应用当中多线程远比多进程应用更为广泛,
- 多线程编程难度高,在多线程环境下需要考虑很多的问题,例如线程安全问题、信号处理的问题等,编写与调试一个多线程程序比单线程程序困难得多
-
线程ID
二、线程相关的操作函数
1、创建线程pthread_create()
- 启动程序时,创建的进程只是一个单线程的进程,称之为初始线程或主线程,
- 主线程可以使用库函数 pthread_create()负责创建一个新的线程, 创建出来的新线程被称为主线程的子线程
- 线程创建成功,新线程就会加入到系统调度队列中,获取到CPU之后就会立马从 start_routine()函数开始运行该线程的任务;调用 pthread_create()函数后,通常我们无法确定系统接着会调度哪一个线程来使用CPU 资源,同步技术解决这个问题。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
#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);
}
2、终止线程pthread_exit()
- 终止线程的方式:
- 线程的start函数执行 return语句并返回指定值,返回值就是线程的退出码;
- 线程调用pthread_exit()函数;**********
- 调用pthread_cancel()取消线程
- 进程中的任意线程调用 exit()、_exit()或者_Exit(),那么将会导致整个进程终止
- pthread_exit()函数将终止调用它的线程
void pthread_exit(void *retval);
#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);
}
3、回收线程 pthread_join()
- 在父、子进程当中,父进程可通过wait()函数(或其变体waitpid())阻塞等待子进程退出并获取其终止状态,回收子进程资源;
- 而在线程当中,也需要如此,通过调用pthread_join()函数来阻塞等待线程的终止,并获取线程的退出码,回收线程资源
- 调用pthread_join()函数将会以阻塞的形式等待指定的线程终止,如果该线程已经终止,则pthread_join()立刻返回。如果多个线程同时尝试调用 pthread_join()等待指定线程的终止,那么结果将是不确定的
- 若线程并未分离,则必须使用 pthread_join()来等待线程终止,回收
线程资源;如果线程终止后,其它线程没有调用pthread_join()函数来回收该线程,那么该线程将变成僵尸线程,与僵尸进程的概念相类似;同样,僵尸线程除了浪费系统资源外,若僵尸线程积累过多,那么会导致应用程序无法创建新的线程 - 当然,如果进程中存在着僵尸线程并未得到回收,当进程终止之后,进程会被其父进程回收,所以僵尸线程同样也会被回收。
- 线程回收的特点pthread_join
- 线程之间关系是对等的。进程中的任意线程均可调用pthread_join()函数来等待另一个线程的终止,如果线程A 创建了线程B,线程B再创建线程 C,那么线程A 可以调用 pthread_join()等待线程 C 的终止,线程 C 也可以调用 pthread_join()等待线程 A 的终止;这与进程间层次关系不同,父进程如果使用 fork()创建了子进程,那么它也是唯一能够对子进程调用 wait()的进程,线程之间不存在这样的关系。
- 不能以非阻塞的方式调用pthread_join()。对于进程,调用waitpid()既可以实现阻塞方式等待、也可以实现非阻塞方式等待。
int pthread_join(pthread_t thread, void **retval);
#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(2);
printf("新线程end\n");
pthread_exit((void *)10);
}
int main(void)
{
pthread_t tid;
void *tret;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
4、取消线程pthread_cancel()
- 在通常情况下,进程中的多个线程会并发执行,每个线程各司其职,直到线程的任务完成之后,该线程中会调用 pthread_exit()退出,或在线程 start函数执行 return语句退出
- 需要向一个线程发送一个请求,要求它立刻退出,我们把这种操作称为取消线程,也就是向指定的线程发送一个请求,要求其立刻终止、退出
int pthread_cancel(pthread_t thread);
#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("新线程--running\n");
for ( ; ; )
sleep(1);
return (void *)0;
}
int main(void)
{
pthread_t tid;
void *tret;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
ret = pthread_cancel(tid);
if (ret) {
fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
- 取消状态以及类型,线程是响应其它线程发送过来的取消请求的,响应请求然后退出线程。当然,线程可以选择不被取消或者控制如何被取消
int pthread_setcancelstate(int state, int *oldstate);
#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)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
for ( ; ; ) {
printf("新线程--running\n");
sleep(2);
}
return (void *)0;
}
int main(void)
{
pthread_t tid;
void *tret;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
ret = pthread_cancel(tid);
if (ret) {
fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
int pthread_setcanceltype(int type, int *oldtype);
- 取消点, 若将线程的取消性类型设置为 PTHREAD_CANCEL_DEFERRED 时(线程可以取消状态下),收到其它线程发送过来的取消请求时,仅当线程抵达某个取消点时,取消请求才会起作用那什么是取消点呢?所谓取消点其实就是一系列函数, 当执行到这些函数的时候,才会真正响应取消请求,这些函数就是取消点;在没有出现取消点时,取消请求是无法得到处理的,究其原因在于系统认为,但
没有到达取消点时,线程此时正在执行的工作是不能被停止的,正在执行关键代码,此时终止线程将可能会导致出现意想不到的异常发生
5、分离线程pthread_detach()
- 默认情况下,当线程终止时,其它线程可以通过调用 pthread_join()获取其返回状态、回收线程资源,有时,程序员并不关系线程的返回状态,只是希望系统在线程终止时能够自动回收线程资源并将其移除。在这种情况下,可以调用pthread_detach()将指定线程进行分离,也就是分离线程
- 处于分离状态的线程,当其终止后,能够自动回收线程资源
int pthread_detach(pthread_t thread);
#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)
{
int ret;
ret = pthread_detach(pthread_self());
if (ret) {
fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
return NULL;
}
printf("新线程start\n");
sleep(2);
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, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
ret = pthread_join(tid, NULL);
if (ret)
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
pthread_exit(NULL);
}
三、线程有关问题
1、注册线程清理处理函数
- 使用atexit()函数注册进程终止处理函数,当进程调用 exit()退出时就会执行进程终止处理函数;其实,当线程退出时也可以这样做,当线程终止退出时,去执行这样的处理函数,我们把这个称为线程清理函数(thread cleanup handler)
- 与进程不同,一个线程可以注册多个清理函数,这些清理函数记录在栈中,每个线程都可以拥有一个清理函数栈,栈是一种先进后出的数据结构,也就是说它们的执行顺序与注册(添加)顺序相反,当执行完所有清理函数后,线程终止
- 线程通过函数pthread_cleanup_push()和pthread_cleanup_pop()分别负责向调用线程的清理函数栈中添加和移除清理函数
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
static void *new_thread_start(void *arg)
{
printf("新线程--start run\n");
pthread_cleanup_push(cleanup, "第1次调用");
pthread_cleanup_push(cleanup, "第2次调用");
pthread_cleanup_push(cleanup, "第3次调用");
sleep(2);
pthread_exit((void *)0);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
}
int main(void)
{
pthread_t tid;
void *tret;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
- 将新线程中调用的pthread_exit()替换为 return,在进行测试,发现并不会执行清理函数
2、线程属性
- 调用 pthread_create()创建线程,可对新建线程的各种属性进行设置。在 Linux 下,使用pthread_attr_t数据类型定义线程的所有属性
- 调用pthread_create()创建线程时,参数 attr设置为NULL,表示使用属性的默认值创建线程
- 如果不使用默认值,参数 attr 必须要指向一个 pthread_attr_t 对象,而不能使用 NULL。当定义 pthread_attr_t 对象之后,需要使用 pthread_attr_init()函数对该对象进行初始化操作,当对象不再使用时,需要使用pthread_attr_destroy()函数将其销毁
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
- pthread_attr_t数据结构中包含的属性比较多,可能比较关注属性包括:线程栈
的位置和大小、线程调度策略和优先级,以及线程的分离状态属性等。 - **线程栈属性 **
每个线程都有自己的栈空间,pthread_attr_t 数据结构中定义了栈的起始地址以及栈大小,pthread_attr_getstack()可以获取这些信息,函数 pthread_attr_setstack()对栈起始地址和栈大小进行设置,
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
static void *new_thread_start(void *arg)
{
puts("Hello World!");
return (void *)0;
}
int main(int argc, char *argv[])
{
pthread_attr_t attr;
size_t stacksize;
pthread_t tid;
int ret;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 4096);
ret = pthread_create(&tid, &attr, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid, NULL);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
pthread_attr_destroy(&attr);
exit(0);
}
- 分离状态属性
- 如果对现已创建的某个线程的终止状态不感兴趣,可以使用
pthread_detach()函数将其分离,那么该线程在退出时,操作系统会自动回收它所占用的资源 - 如果我们在创建线程时就确定要将该线程分离, 可以修改pthread_attr_t结构中的detachstate线程属性,让线程一开始运行就处于分离状态。调用函数 pthread_attr_setdetachstate()设置 detachstate 线程属性,调用
pthread_attr_getdetachstate()获取 detachstate线程属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
puts("Hello World!");
return (void *)0;
}
int main(int argc, char *argv[])
{
pthread_attr_t attr;
pthread_t tid;
int ret;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&tid, &attr, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
pthread_attr_destroy(&attr);
exit(0);
}
3、线程安全==重要
** 线程栈 **
- 进程中创建的每个线程都有自己的栈地址空间,将其称为线程栈
- 譬如主线程调用 pthread_create()创建了一个新的线程,那么这个新的线程有它自己独立的栈地址空间、而主线程也有它自己独立的栈地址空间。
- 既然每个线程都有自己的栈地址空间,那么每个线程运行过程中所定义的自动变量(局部变量)都是分配在自己的线程栈中的,它们不会相互干扰
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
static void *new_thread(void *arg)
{
int number = *((int *)arg);
unsigned long int tid = pthread_self();
printf("当前为<%d>号线程, 线程ID<%lu>\n", number, tid);
return (void *)0;
}
static int nums[5] = {0, 1, 2, 3, 4};
int main(int argc, char *argv[])
{
pthread_t tid[5];
int j;
for (j = 0; j < 5; j++)
pthread_create(&tid[j], NULL, new_thread, &nums[j]);
for (j = 0; j < 5; j++)
pthread_join(tid[j], NULL);
exit(0);
}
可重入函数
- 要解释可重入(Reentrant)函数为何物,首先需要区分单线程程序和多线程程序。
- 单线程程序只有一条执行流(一个线程就是一条执行流),贯穿程序始终;而对于
多线程程序而言,同一进程却存在多条独立、并发的执行流进程中执行流的数量除了与线程有关之外,与信号处理也有关联。因为信号是异步的,进程可能会在其运行过程中的任何时间点收到信号,进而跳转、执行信号处理函数,从而在一个单线程进程 (包含信号处理)中形成了两条(即主程序和信号处理函数)独立的执行流 - 如果一个函数被同一进程的多个不同的执行流同时调用,每次函数调用总是能产生正确的结果(或者叫产生预期的结果),把这样的函数就称为可重入函数
- 重入指的是同一个函数被不同执行流调用,前一个执行流还没有执行完该函数、另一个执行流又开始调用该函数了,其实就是同一个函数被多个执行流并发/并行调用,在宏观角度上理解指的就是被多个执行流同时调用。
- 信号与可重入问题
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static void func(void)
{
}
static void sig_handler(int sig)
{
func();
}
int main(int argc, char *argv[])
{
sig_t ret = NULL;
ret = signal(SIGINT, (sig_t)sig_handler);
if (SIG_ERR == ret) {
perror("signal error");
exit(-1);
}
for ( ; ; )
func();
exit(0);
- 当main()函数正在执行 func()函数代码,此时进程收到了 SIGINT信号,便会打断当前正常执行流程、跳转到 sig_handler()函数执行,进而调用 func、执行 func()函数代码;这里就出现了主程序与信号处理函数并发调用 func()的情况
- 上面的代码是单线程的进程。在信号处理函数中,执行完 func()之后,信号处理函数退出、返回到主程序流程,也就是被信号打断的位置处继续运行。如果每次出现这种情况执行 func()函数都能产生正确的结果,那么 func()函数就是一个可重入函数
- 以上举例说明了函数被多个执行流同时调用的两种情况:
- 在一个含有信号处理的程序当中, 主程序正执行函数 func(),此时进程接收到信号, 主程序被打断,跳转到信号处理函数中执行,信号处理函数中也调用了 func()。
- 在多线程环境下,多个线程并发调用同一个函数
- 需要注意不可重入函数的问题,如果多条
执行流同时调用一个不可重入函数则可能会得不到预期的结果、甚至有可能导致程序崩溃 - 可重入的分类
- 绝对的可重入函数:所谓绝对,指的是该函数不管如何调用,都刚断言它是可重入的,都能得到预期的结果。
- 带条件的可重入函数:指的是在满足某个/某些条件的情况下,可以断言该函数是可重入的,不管怎么调用都能得到预期的结果。
线程安全函数
- 一个函数被多个线程(其实也是多个执行流,但是不包括由信号处理函数所产生的执行流)同时调用时,它总会一直产生正确的结果,把这样的函数称为线程安全函数。
- 譬如下面这个函数是一个不可重入函数,同样也是一个线程不安全函数
static int glob = 0;
static void func(int loops)
{
int local;
int j;
for (j = 0; j < loops; j++) {
local = glob;
local++;
glob = local;
}
}
- 如果对该函数进行修改,使用线程同步技术(譬如互斥锁)对共享变量 glob 的访问进行保护,在读写该变量之前先上锁、读写完成之后在解锁。这样,该函数就变成了一个线程安全函数,但是它依然不是可重入函数,因为该函数更改了外部全局变量的值
- 可重入函数只是单纯从语言语法角度分析它的可重入性质,不涉及到一些具体的实现机制,譬如线程同步技术,这是判断可重入函数和线程安全函数的区别,因为你单从概念上去分析的话,其实可以发现可重入函数和线程安全函数好像说的是同一个东西,“一个函数被多个线程同时调用时,它总会一直产生正确的结果,把这样的函数称为线程安全函数”,多个线程指的就是多个执行流(不包括信号处理函数执行流),所以从这里看跟可重入函数的概念是很相似的
- 判断一个函数是否为线程安全函数的方法是, 该函数被多个线程同时调用是否总能产生正确的结果,如果每次都能产生预期的结果则表示该函数是一个线程安全函数。判读一个函数是否为可重入函数的方法是,从语言语法角度分析,该函数被多个执行流同时调用是否总能产生正确的结果,如果每次都能产生预期的结果则表示该函数是一个可重入函数。
一次性初始化
- 在多线程编程环境下,有些代码段只需要执行一次,譬如一些初始化相关的代码段,通常比较容易想到的就是将其放在 main()主函数进行初始化,这样也就是意味着该段代码只在主线程中被调用,只执行过一次
#include <pthread.h>
pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
static pthread_once_t once = PTHREAD_ONCE_INIT;
static void initialize_once(void)
{
printf("initialize_once被执行: 线程ID<%lu>\n", pthread_self());
}
static void func(void)
{
pthread_once(&once, initialize_once);
printf("函数func执行完毕.\n");
}
static void *thread_start(void *arg)
{
printf("线程%d被创建: 线程ID<%lu>\n", *((int *)arg), pthread_self());
func();
pthread_exit(NULL);
}
static int nums[5] = {0, 1, 2, 3, 4};
int main(void)
{
pthread_t tid[5];
int j;
for (j = 0; j < 5; j++)
pthread_create(&tid[j], NULL, thread_start, &nums[j]);
for (j = 0; j < 5; j++)
pthread_join(tid[j], NULL);
exit(0);
}
** 线程特有数据 **
- 线程特有数据也称为线程私有数据, 简单点说,就是为每个调用线程分别维护一份变量的副本(copy) ,每个线程通过特有数据键(key)访问时,这个特有数据键都会获取到本线程绑定的变量副本。这样就可以避免变量成为多个线程间的共享数据。
** 线程局部存储** - 通常情况下,程序中定义的全局变量是进程中所有线程共享的,所有线程都可以访问这些全局变量;而线程局部存储在定义全局或静态变量时,使用__thread 修饰符修饰变量,此时,每个线程都会拥有一份对该变量的拷贝。线程局部存储中的变量将一直存在,直至线程终止,届时会自动释放这一存储
- 线程局部存储的主要优点在于,比线程特有数据的使用要简单。要创建线程局部变量,只需简单地在全局或静态变量的声明中包含__thread修饰符即可
- 关于线程局部变量的声明和使用,需要注意以下几点:
- 如果变量声明中使用了关键字 static或 extern,那么关键字__thread必须紧随其后
- 与一般的全局或静态变量申明一眼,线程局部变量在申明时可设置一个初始值
- 可以使用C 语言取值操作符(&)来获取线程局部变量的地址
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
static __thread char buf[100];
static void *thread_start(void *arg)
{
strcpy(buf, "Child Thread\n");
printf("子线程: buf (%p) = %s", buf, buf);
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t tid;
int ret;
strcpy(buf, "Main Thread\n");
if (ret = pthread_create(&tid, NULL, thread_start, NULL)) {
fprintf(stderr, "pthread_create error: %d\n", ret);
exit(-1);
}
if (ret = pthread_join(tid, NULL)) {
fprintf(stderr, "pthread_join error: %d\n", ret);
exit(-1);
}
printf("主线程: buf (%p) = %s", buf, buf);
exit(0);
}
参考资料:正点原子linuxC应用编程
|