线程
为什么使用线进程,什么是线程
操作系统的设计
- 以多进程形式,允许多个任务同时运行;
- 以多线程形式,允许单个任务分成不同的部分运行;
- 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
使用多线程编程的弊端
- 进程间切换开销大,通常对于一些中小型应用程序来说不划算。
- 进程间通信较为麻烦。 每个进程都在各自的地址空间中、相互独立、隔离,处在于不同的地址空间
多线程编程够弥补上面的问题
- 同一进程的多个线程间切换开销比较小。
- 同一进程的多个线程间通信容易。 它们共享了进程的地址空间,所以它们都是在同一个地址空间 中,通信容易。
- 线程创建的速度远大于进程创建的速度。
- 多线程在多核处理器上更有优势!
多线程编程的弊端
- 线程也有它的缺点、劣势, 譬如多线程编程难度高,对程序员的编程功底要求比较高,因为在多线程环境下 需要考虑很多的问题。
- 例如线程安全问题、信号处理的问题等, 编写与调试一个多线程程序比单线程程序困 难得多。
多线程多进程使用场景
- 对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
- 要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。
- 多进程编程通常会用在一些大型应用程序项目中,譬如网络服务器应用程序,在中小型应用程序中用的比较少。
什么是线程
- 线程是参与系统调度的最小单位。进程是资源分配的最小单位
- 一个进程中可以创建多个线程, 多个线程实现并发运行, 每个线程执行不同的任务。
- . 譬如某应用程序设计了两个需要并发运行的任务 task1 和 task2,可将两个不同的任务分别放置在两个线程中。
- 有点类似freertos里面的任务
线程的特点
- 线程是程序最基本的运行单位,而进程不能运行, 真正运行的是进程中的线程。 当启动应用程序后,系统就创建了一个进程,可以认为进程仅仅是一个容器, 它包含了线程运行所需的数据结构、环境变量等信息。
- 可并发执行。同一进程的多个线程之间可并发执行,在宏观上实现同时运行的效果;
- 共享进程资源。
串行 并发 并行
串行
它指的是一种顺序执行,譬如先完成 task1,接着做 task2、直到完task2,然 后做 task3、直到完成,必须要完成上一件事才能去做下一件事,只 有一个执行单元,这就是串行运行。
并发
每个任务执行一段时间,时间一到则切换执行下一个任务,依次这样轮训(交叉/交替执行) ,这就是并发运行。
并行
并行与串行则截然不同,并行指的是可以并排/并列执行多个任务, 这样的系统,它通常有多个执行单元, 所以可以实现并行运行,譬如并行运行 task1、 task2、 task3。并行运行并不一定要同时开始运行、同时结束运行,只需满足在某一个时间段上存在多个任务被多个执行单元同时在运行着。
形象生动
- 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接电话,这就说明不支持并发也不支持并行,仅仅只是串行。
- 你吃饭吃到一半,电话来了,你停下吃饭去接了电话,电话接完后继续吃饭,这说明你支持并发。
- 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
⚫ 串行:一件事、一件事接着做
⚫ 并发:交替做不同的事;
⚫ 并行:同时做不同的事。
perror和strerror
- 在库函数中有个errno的全局变量,每个errno的值对应错误的类型。
- 当我们调用某些函数出错时,该函数就设置了errno的值,perror就将errno值对应的错误类型打印出来(这也是perror要紧跟着函数调用的原因);
- 而在另外一些函数中,函数出错并不设置errno的值,而是通过返回错误类型对应的值,来得到错误的类型,在这种情况下,我们就要使用strerror(线程就是这类)
不知道哪一个函数用哪一个可以man一下这个函数,搜索return value
open函数:这类可以使用perror
pthread_create函数:这类就不能使用perror
1.首先通过 / 查找,如:/return value
2.然后用n查找下一个,用N查找上一个
创建线程
/*
*@param thread: pthread_t 类型指针,当 pthread_create()成功返回时,
新创建的线程的线程 ID 会保存在参数 thread所指向的内存中,
后续的线程相关函数会使用该标识来引用此线程。
*@param attr: pthread_attr_t 类型指针,指向 pthread_attr_t 类型的缓冲区,
pthread_attr_t 数据类型定义了线程的各种属性,如果将参数 attr 设置为 NULL,
那么表示将线程的所有属性设置为默认值,以此创建新线程。
*@param start_routine: 参数 start_routine 是一个函数指针,指向一个函数, 新创建的线程从 start_routine()函数开始运行,
该函数返回值类型为void *,并且该函数的参数只有一个void *,其实这个参数就是pthread_create()函数的第四个参数 arg。
如果需要向 start_routine()传递的参数有一个以上,那么需要把这些参数放到一个结构体中,然后把这个结构体对象的地址作为 arg 参数传入。
*@param arg: 传递给start_routine()函数的参数。一般情况下,需要将 arg 指向一个全局或堆变量,
意思就是说在线程的生命周期中,该 arg 指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。
然也可将参数 arg 设置为 NULL,表示不需要传入参数给 start_routine()函数。
*@return 成功返回 0;失败时将返回一个错误号,并且参数 thread 指向的内容是不确定的。
*/
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
//测试新进程id与函数得到id是否匹配
pthread_t tid;
static void *new_thread_start(void *arg)
{
printf("新线程:进程ID<%d> 全局线程ID<%lu> 函数线程ID<%lu>\n", getpid(), tid,pthread_self());
return (void*)0; //执行 return 语句终止线程
}
int main(void)
{
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret)
{
//在标准 I/O 中,可以使用 stdin、 stdout、 stderr 来表示标准输入、标准输出和标准错误。
//fprintf()可将格式化数据写入到由 FILE 指针指定的文件中,譬如将字符串“Hello World”写入到标准错误:
fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
exit(-1);
}
printf("主线程:进程ID<%d> 线程ID<%lu>\n", getpid(), pthread_self());
sleep(1);
//延时,new_thread_start线程函数执行完等待一会,如果进程中的任意线程调用 exit()、 _exit()或者_Exit(),那么将会导致整个进程终止,这里需要注意!这里的延时也不会执行
exit(0);
}
编译时出现了错误,提示“对‘pthread_create’未定义的引用”,示例代码确实已经包含了<pthread.h>头文件,报错是出现在程序代码链接时、而并非是编译过程,所以可知这是链接库的文件,
使用-l 选项指定链接库 pthread,原因在于 pthread 不在 gcc 的默认链接库中,所以需要手动指定。
终止线程
/*
*@param retval: 参数 retval 的数据类型为 void *,指定了线程的返回值、也就是程的退出码,该返回值可由另一个线程通过调用 pthread_join()来获取;
同理,如果线程是在 start 函数中执行 return 语句终止,那么 return 的返回值也是可以通过 pthread_join()来获取的。
*/
// void pthread_exit(void *retval);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
static void *new_thread_start(void *arg)
{
puts("新线程");
//睡眠1秒钟,给主线程退出时间
sleep(1);
puts("新线程退出");
//退出新线程
pthread_exit(NULL);
puts("新线程我没了");
}
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);
}
puts("主线程退出");
//退出主线程
pthread_exit(NULL);
puts("主线程我没了");
exit(0);
}
pthread_exit()终止之后,整个进程并没有结束,而新线程还在继续运行。
回收线程
/*
*@param thread: pthread_join()等待指定线程的终止,通过参数 thread(线程 ID) 指定需要等待的线程;
*@param retval: 如果参数 retval 不为 NULL,则 pthread_join()将目标线程的退出状态(即目标线程通过pthread_exit()退出时指定的返回值或者在线程 start 函数中执行 return 语句对应的返回值)
复制到*retval 所指向的内存区域;如果目标线程被 pthread_cancel()取消, 则将 PTHREAD_CANCELED 放在*retval 中。 如果对目标线程的终止状态不感兴趣,则可将参数 retval 设置为 NULL。
*@return 成功返回 0;失败将返回错误码。
*/
//int pthread_join(pthread_t thread, void **retval);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
static void *new_thread_start(void *arg)
{
puts("新线程");
//睡眠1秒钟,给主线程退出时间
sleep(2);
puts("新线程退出");
//退出新线程
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)
{
perror("pthread_create error");
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_join()阻塞等待新线程终止,新线程终止后,pthread_join()返回,将目标线程的退出码保存在*tret 所指向的内存中。
取消线程
取消一个进程
/*
*@brief :函数pthread_cancel()立即返回,不会等待目标线程的退出。默认情况下,目标线程也会立刻退出,
其行为表现为如同调用了参数为 PTHREAD_CANCELED(其实就是(void *)-1)的pthread_exit()函数,
但是,线程可以设置自己不被取消或者控制如何被取消,所以pthread_cancel()并不会等待线程终止,仅仅只是提出请求。
*@param[thread] :pthread_join()等待指定线程的终止,通过参数 thread(线程 ID) 指定需要等待的线程;
*@return :成功返回 0;失败将返回错误码。
*/
// int pthread_cancel(pthread_t thread);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
static void *new_thread_start(void *arg)
{
puts("新线程死循环执行");
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);
}
//取消一个线程
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);
}
当主线程发送取消请求之后,新线程便退出了,而且退出码为-1,也就是 PTHREAD_CANCELED。注意死循环里的sleep函数,没有该函数将无法取消,有该函数也就是有一个取消点。
取消状态以及类型
/*
*@brief :pthread_setcancelstate()函数会将调用线程的取消性状态设置为参数 state 中给定的值,
并将线程之前的取消性状态保存在参数 oldstate 指向的缓冲区中, 如果对之前的状态不感兴趣, Linux 允许将参数 oldstate 设置为 NULL;
*@param[state] :PTHREAD_CANCEL_ENABLE: 线程可以取消,这是新创建的线程取消性状态的默认值,所以新建线程以及主线程默认都是可以取消的。
PTHREAD_CANCEL_DISABLE: 线程不可被取消,如果此类线程接收到取消请求,则会将请求挂起,直至线程的取消性状态变为 PTHREAD_CANCEL_ENABLE。
*@param[oldstate] :指向的缓冲区中, 如果对之前的状态不感兴趣, Linux 允许将参数 oldstate 设置为 NULL;
*@return :成功返回 0;失败将返回错误码。
*/
// int pthread_setcancelstate(int state, int *oldstate);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
static void *new_thread_start(void *arg)
{
puts("新线程设置为不可取消状态");
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
for (;;)
{
puts("新线程死循环执行");
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_cancel error:%s\n", strerror(ret));
exit(-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);
}
取消点
/*
*@brief :pthread_setcancelstate()函数会将调用线程的取消性状态设置为参数 state 中给定的值,
并将线程之前的取消性状态保存在参数 oldstate 指向的缓冲区中, 如果对之前的状态不感兴趣, Linux 允许将参数 oldstate 设置为 NULL;
*@param[type] :PTHREAD_CANCEL_DEFERRED: 取消请求到来时,线程还是继续运行,取消请求被挂起,直到线程到达某个取消点(cancellation point,将在 11.6.3 小节介绍) 为止,这是所有新建线程包括主线程默认的取消性类型。
PTHREAD_CANCEL_ASYNCHRONOUS: 可能会在任何时间点(也许是立即取消,但不一定)取消线程,这种取消性类型应用场景很少, 不再介绍!
*@param[oldstate] :指向的缓冲区中, 如果对之前的状态不感兴趣, Linux 允许将参数 oldstate 设置为 NULL;
*@return :成功返回 0;失败将返回错误码。
*/
// int pthread_setcanceltype(int type, int *oldstate);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
static void *new_thread_start(void *arg)
{
puts("新线程设置为取消点到来时取消,也就是调用sleep函数puts函数等");
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
for (;;)
{
puts("新线程死循环执行");
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);
}
//取消一个线程
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);
}
线程可取消性的检测
/*
*brief :假设线程执行的是一个不含取消点的循环(譬如 for 循环、 while 循环),那么这时线程永远也不会响应取消请求,
此时可以使用 pthread_testcancel(),该函数目的很简单,就是产生一个取消点,线程如果已有处于挂起状态的取消请求,那么只要调用该函数,线程就会随之终止。
*/
//void pthread_testcancel(void);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
static void *new_thread_start(void *arg)
{
puts("新线程死循环执行");
for (;;)
{
//不调用该函数线程无法取消,调用该函数就会产生一个取消点进行取消检测
pthread_testcancel();
}
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);
}
//取消一个线程
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);
}
死循环里没有取消点函数,那该线程需要调用生成取消点函数来取消
分离线程
/*
*@brief :默认情况下,当线程终止时,其它线程可以通过调用 pthread_join()获取其返回状态、回收线程资源,有时,程序员并不关系线程的返回状态,
只是希望系统在线程终止时能够自动回收线程资源并将其移除。在这种情况下,可以调用 pthread_detach()将指定线程进行分离
*@param[thread] :pthread_join()等待指定线程的终止,通过参数 thread(线程 ID) 指定需要等待的线程;
*@return :成功返回 0;失败将返回错误码。
*@other :一个线程既可以将另一个线程分离,一旦线程处于分离状态,就不能再使用 pthread_join()来获取其终止状态,此过程是不可逆的,
一旦处于分离状态之后便不能再恢复到之前的状态。处于分离状态的线程,当其终止后,能够自动回收线程资源。
*/
//int pthread_detach(pthread_t thread);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.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));
}
puts("新线程分离开始执行");
sleep(2);
puts("新线程终止");
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);
}
//确保新线程执行了分离函数
sleep(1);
//回收一个线程
ret = pthread_join(tid, &tret);
if (ret)
{
fprintf(stderr,"pthread_join error:%s\n",strerror(ret));
sleep(3);
printf("新线程终止, code=%ld\n", (long)tret);
pthread_exit(NULL);
}
}
注册线程清理处理函数
/*
*@brief :pthread_cleanup_push()和 pthread_cleanup_pop()分别负责向调用线程的清理函数栈中添加和移除清理函数
*@param[routine] :routine 是一个函数指针,指向一个需要添加的清理函数;
*@param[arg] :当调用清理函数 routine()时, 将 arg 作为 routine()函数的参数。
*@others :1. 当线程执行以下动作时,清理函数栈中的清理函数才会被执行:
线程调用 pthread_exit()退出时;
线程响应取消请求时;
用非 0 参数调用 pthread_cleanup_pop()
2. 必须一一对应着来使用
*/
//void pthread_cleanup_push(void (*routine)(void *), void *arg);
//void pthread_cleanup_pop(int execute);
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
static void cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
static void *new_thread_start(void *arg)
{
puts("新线程--start run");
pthread_cleanup_push(cleanup, "第 1 次调用");
pthread_cleanup_push(cleanup, "第 2 次调用");
pthread_cleanup_push(cleanup, "第 3 次调用");
pthread_cleanup_pop(1);//传入非零值,清理函数工作,执行最顶层的清除函数
puts("===================");
sleep(2);
pthread_exit((void *)0); //线程终止, 如果这里调用return,发现并不会执行清理函数。
/* 为了与 pthread_cleanup_push 配对,不添加程序编译会通不过 */
//线程退出开始执行
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);
}
线程属性
线程栈属性
/*
*@brief :pthread_attr_init()函 数 对 该对象进 行初始 化操作 ,当对象 不再使 用时 , 需要使用pthread_attr_destroy()函数将其销毁
获取设置栈的起始地址以及栈大小。
*@param[attr] :调用 pthread_create()创建线程时,参数 attr 设置为 NULL, 表示使用属性的默认值创建线程。 如果不使用默认值,参数 attr 必须要指向一个 pthread_attr_t 对象;
*@param[stackaddr] :栈起始地址。
*@param[stacksize] :栈大小
*return :成功返回 0,失败将返回一个非 0 值的错误码.
*/
//int pthread_attr_init(pthread_attr_t *attr);
//int pthread_attr_destroy(pthread_attr_t *attr);
//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);
/*创建新的线程,将线程的栈大小设置为 4Kbyte。*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
static void *new_thread_start(void *arg)
{
puts("新线程--start run");
pthread_exit((void *)0);
}
int main(void)
{
pthread_t tid;
pthread_attr_t attr;
void *stackaddr;
size_t stacksize;//这里不能定义为指针变量 定义为指针变量,执行会报错Segmentation fault (core dumped)
void *tret;
int ret;
//线程属性函数初始化
pthread_attr_init(&attr);
/* 设置栈大小为 4K */
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);
}
// pthread_attr_getstack(&attr,&stackaddr,&stacksize); //栈起始地址:(nil) 栈大小:0
// pthread_attr_getstackaddr(&attr, &stackaddr);
// pthread_attr_getstacksize(&attr, &stacksize); 栈起始地址:(nil) 栈大小:8388608
// printf("栈起始地址:%p 栈大小:%ld\n",stackaddr,stacksize);
//回收一个线程
ret = pthread_join(tid, &tret);
if (ret)
{
fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
/* 销毁 attr 对象 */
pthread_attr_destroy(&attr);
exit(0);
}
分离状态属性
/*
*@brief :如果对现已创建的某个线程的终止状态不感兴趣,可以使用pthread_detach()函数将其分离,那么该线程在退出时,操作系统会自动回收它所占用的资源。
创建线程时就确定要将该线程分离, 可以修改 pthread_attr_t 结构中的 detachstate 线程属性,让线程一开始运行就处于分离状态
*@param[attr] :调用 pthread_create()创建线程时,参数 attr 设置为 NULL, 表示使用属性的默认值创建线程。 如果不使用默认值,参数 attr 必须要指向一个 pthread_attr_t 对象;
*@param[detachstate] :取值如下:
PTHREAD_CREATE_DETACHED: 新建线程一开始运行便处于分离状态, 以分离状态启动线程,
无法被其它线程调用 pthread_join()回收,线程结束后由操作系统收回其所占用的资源;
PTHREAD_CREATE_JOINABLE: 这是 detachstate 线程属性的默认值, 正常启动线程,可以被
其它线程获取终止状态信息。
*return :成功返回 0,失败将返回一个非 0 值的错误码.
*/
// int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
// int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
/*创建新的线程,将线程的栈大小设置为 4Kbyte。*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
static void *new_thread_start(void *arg)
{
puts("新线程--start run");
pthread_exit(NULL);
}
int main(void)
{
pthread_t tid;
pthread_attr_t attr;
void *tret;
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);
/* 销毁 attr 对象 */
pthread_attr_destroy(&attr);
exit(0);
}
线程安全
线程栈
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#define PTHREAD_SIZE 5
static void *new_thread_start(void *arg)
{
int number = *((int *)arg);
unsigned long tid = pthread_self();
printf("第%d号线程,线程ID<%ld>\n", number, tid);
pthread_exit((void *)number);
}
int main(void)
{
pthread_t tid[PTHREAD_SIZE];
int pthread_number[PTHREAD_SIZE] = {1, 2, 3, 4, 5};
int i;
void *tret;
int ret;
//申请5个线程
/*这 5 个线程使用同一个 start 函数 new_thread, 该函数中定义了局部变量 number 和 tid 以及 arg 参数,意味着这 5
个线程的线程栈中都各自为这些变量分配了内存空间,任何一个线程修改了 number 或 tid 都不会影响其它线程。*/
for (i = 0; i < PTHREAD_SIZE; i++)
{
ret = pthread_create(&tid[i], NULL, new_thread_start, (void *)&pthread_number[i]);
if (ret)
{
fprintf(stderr, "%d pthread_create error:%s\n", i, strerror(ret));
exit(-1);
}
}
//回收5个线程
for (i = PTHREAD_SIZE - 1; i >= 0; i--)
{
ret = pthread_join(tid[i], &tret);
if (ret)
{
fprintf(stderr, "%d pthread_join error:%s\n", i, strerror(ret));
exit(-1);
}
printf("回收线程%ld\n", (long)tret);
}
exit(0);
}
可重入函数
绝对可重入函数的特点:
⚫ 函数内所使用到的变量均为局部变量,换句话说,该函数内的操作的内存地址均为本地栈地址;
⚫ 函数参数和返回值均是值类型;
⚫ 函数内调用的其它函数也均是绝对可重入函数。
线程安全函数
一次性初始化
/*
*@param [once_control] :这是一个 pthread_once_t 类型指针,在调用 pthread_once()函数之前,我们需要定义了一个 pthread_once_t 类型的静态变量,调用 pthread_once()时参数 once_control 指向该变量。
通常在定义变量时会使用 PTHREAD_ONCE_INIT 宏对其进行初始化,
其初值不是 PTHREAD_ONCE_INIT,pthread_once()的行为将是不正常的.
*@param [retval] :一个函数指针,参数 init_routine 所指向的函数就是要求只能被执行一次的代码段,
pthread_once()函数内部会调用 init_routine(),即使 pthread_once()函数会被多次执行,但它能保证 init_routine()仅被执行一次。
*/
// int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#define PTHREAD_SIZE 5
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static void initialize_once(void)
{
printf("被线程ID<%ld>调用\n", pthread_self());
}
static void func(void)
{
pthread_once(&once_control, initialize_once); //执行一次性初始化函数
printf("函数 func 执行完毕.\n");
}
static void *new_thread_start(void *arg)
{
printf("第%d号线程,线程ID<%ld>\n", *((int *)arg), pthread_self());
func();
pthread_exit(NULL);
}
int main(void)
{
pthread_t tid[PTHREAD_SIZE];
int pthread_number[PTHREAD_SIZE] = {1, 2, 3, 4, 5};
int i;
int ret;
//申请5个线程
/*这 5 个线程使用同一个 start 函数 new_thread, 该函数中定义了局部变量 number 和 tid 以及 arg 参数,意味着这 5
个线程的线程栈中都各自为这些变量分配了内存空间,任何一个线程修改了 number 或 tid 都不会影响其它线程。*/
for (i = 0; i < PTHREAD_SIZE; i++)
{
ret = pthread_create(&tid[i], NULL, new_thread_start, (void *)&pthread_number[i]);
if (ret)
{
fprintf(stderr, "%d pthread_create error:%s\n", i, strerror(ret));
exit(-1);
}
}
//回收5个线程
for (i = PTHREAD_SIZE - 1; i >= 0; i--)
{
ret = pthread_join(tid[i], NULL);
if (ret)
{
fprintf(stderr, "%d pthread_join error:%s\n", i, strerror(ret));
exit(-1);
}
}
exit(0);
}
线程特有数据
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#define MAX_ERROR_LEN 256
static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerror_key;
static void destructor(void *buf)
{
free(buf); //释放内存
}
static void create_key(void)
{
/* 创建一个键(key),并且绑定键的解构函数 */
if (pthread_key_create(&strerror_key, destructor))
pthread_exit(NULL);
}
/******************************
* 对 strerror 函数重写
* 使其变成为一个线程安全函数
******************************/
static char *strerror(int errnum)
{
char *buf;
/* 创建一个键(只执行一次 create_key) */
if (pthread_once(&once, create_key))
pthread_exit(NULL);
/* 获取 */
buf = pthread_getspecific(strerror_key);
if (NULL == buf)
{ //首次调用 my_strerror 函数,则需给调用线程分配线程私有数据
buf = malloc(MAX_ERROR_LEN); //分配内存
if (NULL == buf)
pthread_exit(NULL);
/* 保存缓冲区地址,与键、线程关联起来 */
if (pthread_setspecific(strerror_key, buf))
pthread_exit(NULL);
}
if (errnum < 0 || errnum >= _sys_nerr || NULL == _sys_errlist[errnum])
snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", errnum);
else
{
strncpy(buf, _sys_errlist[errnum], MAX_ERROR_LEN - 1);
buf[MAX_ERROR_LEN - 1] = '\0'; //终止字符
}
return buf;
}
线程局部存储
#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);
}