文章目录
- 一、线程概念
- NPTL 介绍
- 线程的本质
- 线程共享与非共享资源
- 线程的优缺点
- 二、线程基础 API
- 线程操作
- 线程号
- 线程的创建
- 线程资源回收
- 线程分离
- 线程终止
- 线程取消
- 线程属性
- 概述
- 属性初始化和销毁
- 分离状态
- 线程栈地址
- 线程栈大小
- 综合参考程序
- 线程使用注意事项
一、线程概念
与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。在许多经典的操作系统教科书中,总是把进程定义为程序的执行实例,它并不执行什么,只是维护应用程序所需的各种资源,而线程则是真正的执行实体。所以,线程是轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。
进程,直观点说,是保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体有自己的地址空间,有自己的堆,上级挂靠单位是操作系统。操作系统会以进程为单位,分配系统资源,所以我们也说,进程是CPU分配资源的最小单位。而线程存在于进程当中(进程可以认为是线程的容器),是操作系统调度执行的最小单位。说通俗点,线程就是干活的。
- 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
- 线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
- 为了让进程完成一定的工作,进程必须至少包含一个线程;线程自己基本上不拥有系统资源,所以那么线程必须属于某一个进程:如果说进程是一个资源管家,负责从主人那里要资源的话,那么线程就是干活的苦力。一个管家必须完成一项工作,就需要最少一个苦力,也就是说,一个进程最少包含一个线程,也可以包含多个线程。苦力要干活,就需要依托于管家,所以说一个线程,必须属于某一个进程。
- 进程有自己的地址空间,线程使用进程的地址空间,也就是说同一个进程中的所有线程共享同一份全局内存区域,进程里的资源,线程都是有权访问的,比如说堆啊,栈啊,静态存储区,初始化数据段、未初始化数据段什么的。这样同一个程序中的所有线程均会独立执行相同程序。
总结:进程是操作系统分配资源的最小单位;线程是操作系统调度的最小单位
NPTL 介绍
当 Linux 最初开发时,在内核中并不能真正支持线程。但是它的确可以通过 clone()
系统调用将进程作为可调度的实体。这个调用创建了调用进程(calling process)的一个拷贝,这个拷贝与调用进程共享相同的地址空间。LinuxThreads 项目使用这个调用来完全在用户空间模拟对线程的支持。不幸的是,这种方法有一些缺点,尤其是在信号处理、调度和进程间同步原语方面都存在问题。另外,这个线程模型也不符合 POSIX 标准的要求。
要改进 LinuxThreads,非常明显需要内核的支持,并且需要重写线程库。有两个相互竞争的项目开始来满足这些要求。一个包括 IBM 的开发人员的团队开展了 NGPT(Next-Generation POSIX Threads)项目。同时,Red Hat 的一些开发人员开展了 NPTL 项目。NGPT 在 2003 年中期被放弃了,把这个领域完全留给了 NPTL。
NPTL,或称为 Native POSIX Thread Library,是 Linux 线程的一个新实现,它克服了 LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相比,它在性能和稳定性方面都提供了重大的改进。
查看当前pthread库版本:
gsmrlab@amsiv:~$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.27
查看指定进程的 LWP(light weight process) 号:
ps -Lf pid
linux 下安装 posix 手册
查看线程相关函数列表,需要先下载 posix 手册:
sudo apt-get install manpages-posix-dev
【说明】manpages-posix-dev 包含 POSIX 的 header files 和 library calls 的用法
查看 posix 线程函数列表:
man -k pthread
查看函数的具体用法:
man [函数名]
线程的本质
线程与进程的联系
类 Unix 系统中,早期是没有“线程”概念的,80 年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切:
- 线程是轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都使用
clone()
系统调用:实际上,无论是创建进程的fork()
,还是创建线程的pthread_create()
,底层实现都是调用同一个内核函数clone()
。- 如果复制对方的地址空间,那么就产出一个“进程”;
- 如果共享对方的地址空间,就产生一个“线程”。
- 从内核里看进程和线程是一样的,都有各自不同的PCB。Linux 内核是不区分进程和线程的,只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用(即线程在底层本质就是一个进程,只是在应用层之上是一个线程;如果在底层做的是深拷贝(子进程拷贝父进程)就是一个进程;如果在底层做的是浅拷贝就是一个线程)。
- 进程可以蜕变成线程
- 在linux下,线程最是小的执行单位;进程是最小的分配资源单位
线程与进程的区别
当然进程与线程也有不同,线程和进程主要区别如下:
- 进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
- 调用
fork()
来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork()
调用在时间上的开销依然不菲。 - 线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。
- 创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。
线程共享与非共享资源
线程共享资源
- 进程 ID 和父进程 ID
- 进程组 ID 和会话 ID
- 用户 ID 和 用户组 ID
- 文件描述符表
- 信号处置:每种信号的处理方式
- 文件系统的相关信息:文件权限掩码(umask)、当前工作目录
- 虚拟地址空间(除了栈和.text)
线程与进程的联系
- 线程 Id
- 信号掩码
- 线程特有数据
- 独立的栈空间(用户空间栈),本地变量和函数的调用链接信息
- errno变量
- 信号屏蔽字
- 调度优先级
【注意】线程间的共享资源与非共享资源是针对同一进程中的线程而言的,如果两个线程不在同一进程中,就谈不上共享资源与非共享资源。
线程的优缺点
- 优点:
- 提高程序并发性
- 开销小
- 数据通信、共享数据方便
- 缺点:
- 库函数,不稳定
- 调试、编写困难、gdb 不支持
- 对信号支持不好
二、线程基础 API
线程操作
线程函数的程序在 pthread 库中,pthread 库不是Linux系统默认的库,链接时需要使用静态库 libpthread.a,故编译时需要指定链接线程库:要加上参数 -lpthread(当然直接-pthread也可)。
线程号
就像每个进程都有一个进程号一样,每个线程也有一个线程号。进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进程环境中有效。
进程号用 pid_t 数据类型表示,是一个非负整数;线程号则用 pthread_t 数据类型来表示,linux 使用无符号长整数表示。有的系统在实现 pthread_t 的时候,用一个结构体来表示,所以在可移植的操作系统实现中,不能把它做为整数处理。
获取线程号的函数如下:
#include <pthread.h>pthread_t pthread_self(void);
功能:获取线程号。
参数:无
返回值:调用线程的线程 ID 。
【注意】本函数总是会执行成功。
判断两线程是否相等的函数如下:
#include <pthread.h>int pthread_equal(pthread_t t1, pthread_t t2);
功能:判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
参数:t1,t2:待判断的线程号。
返回值:相等: 非 0不相等:0
参考程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <pthread.h>int main() {pthread_t thread_id = -1;// 返回调用线程的线程IDthread_id = pthread_self(); //本函数总是会执行成功,所以不需要进行错误判断printf("Thread ID = %lu \n", thread_id);if (0 != pthread_equal(thread_id, pthread_self())) {printf("Equal!\n");} else {printf("Not equal!\n");}return 0;
}
线程的创建
一般情况下,main 函数所在的线程我们称之为主线程(或者叫做 main 线程),其余创建的线程称之为子线程。
程序中默认只有一个进程,fork()
函数调用之后,程序中会存在两个进程;程序中默认只有一个线程,pthread_create()
函数调用,该进程中会存在两个线程。
pthread_create 函数用于创建线程,详细如下所示:
#include <pthread.h>int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg );
功能:创建一个线程。
参数:thread:线程标识符地址(传出参数:线程创建成功后,子线程的线程ID被写到该变量中)。attr:线程属性结构体地址,通常设置为 NULL。start_routine:线程函数的入口地址。arg:传给线程函数的参数。
返回值:成功:0失败:非0,即返回错误号。这个错误号和之前errno不太一样。获取错误号的信息: char * strerror(int errnum);
在一个线程中调用 pthread_create()
创建新的线程后,当前线程从 pthread_create()
返回继续往下执行,而新的线程所执行的代码由我们传给 pthread_create 的函数指针 start_routine 决定。
【注意】由于 pthread_create 的错误码不保存在 errno 中,因此不能直接用 perror()
打印错误信息,可以先用 strerror()
把错误码转换成错误信息再打印。
参考程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <pthread.h>// 回调函数
void *thread_fun(void * arg) {sleep(1);int num = *((int *)arg);printf("int the new thread: num = %d\n", num);return NULL;
}int main()
{pthread_t tid = -1;int test = 100;// 返回错误号int ret = pthread_create(&tid, NULL, thread_fun, (void *)&test);if (ret != 0) {printf("error number: %d\n", ret);// 根据错误号打印错误信息printf("error information: %s\n", strerror(ret));}while (1);return 0;
}
线程资源回收
创建线程会申请系统资源,在线程调用结束之后,需要将这些资源进行回收,pthread_join 函数可以实现资源的回收。
为什么要进行连接?父进程创建了多个子进程,当这些子进程运行结束后,父进程就需要负责回收这些进程的资源。同理父线程也需要回收子线程的资源,否则会产生僵尸线程。与进程不同,子线程可以被任何其他的线程回收,一般情况下都是父线程回收子线程。所以需要连接子线程并对其资源进程回收。
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待线程结束,并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
参数:thread:被等待的线程号。retval:用来存储线程退出状态的指针的地址。thread 线程以不同的方法终止,通过 pthread_join 得到的终止状态是不同的,总结如下:1.如果thread线程通过return返回,retval 所指向的单元里存放的是 thread 线程函数的返回值。2. 如果 thread 线程被别的线程调用 pthread_cancel 异常终止掉,retval 所指向的单元里存放的是常数PTHREAD_CANCELED。3. 如果 thread 线程是自己调用 pthread_exit 终止的,retval 所指向的单元存放的是传给 pthread_exit 的参数。返回值:成功:0失败:非 0
参考程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>void *thead(void *arg) {static int num = 123; //静态变量sleep(3);printf("after 3 seceonds, thread will return\n");return # // 注意返回的变量,不能是局部变量
}int main()
{pthread_t tid = -1;int ret = -1;void *value = NULL;// 创建线程ret = pthread_create(&tid, NULL, thead, NULL);if (0 != ret) {printf("pthread_creat failed......\n");return 1;}// 阻塞等待线程号为 tid 的线程结束(如果此线程结束就回收其资源) // &value保存线程退出的返回值pthread_join(tid, &value);printf("main pthrad will exit,value = %d\n", *((int *)value));return 0;
}
调用该函数的线程将挂起等待,直到 id 为 thread 的线程终止。
线程分离
使用 pthread_join 函数回收资源会导致调用 pthread_join 函数的线程产生阻塞等待,但是很多时候我们并不希望调用线程被阻塞,这时候就需要使用线程分离。所以线程分离不需要让父线程或者其他线程手动调用 pthread_join 去连接当前的线程来回收资源,设置分离后自动回收资源。
一般情况下,线程终止后,其终止状态一直保留到其它线程调用 pthread_join 获取它的状态为止。但是线程也可以被置为 detach 状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
【注意】
- 不能对一个已经处于 detach 状态的线程调用 pthread_join,这样的调用将返回 EINVAL 错误。也就是说,如果已经对一个线程调用了 pthread_detach 就不能再调用 pthread_join 了。
- 相对于线程资源回收函数:pthread_join,线程分离函数也可以达到资源回收的能力,但是线程分离无法获取线程函数的返回值。
#include <pthread.h>int pthread_detach(pthread_t thread);
功能:使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,回收线程的函数不会阻塞。
参数:thread:线程号。
返回值:成功:0失败:非0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>void *thead(void *arg) {static int num = 123; //静态变量sleep(3);printf("after 3 seceonds, thread will return\n");return #
}int main() {pthread_t tid = -1;int ret = -1;void *value = NULL;// 创建线程ret = pthread_create(&tid, NULL, thead, NULL);if (0 != ret) {printf("pthread_creat failed......\n");return 1;}// 线程资源由系统回收,主线程不会阻塞pthread_detach(tid);printf("main pthrad will exit\n");while (1) {sleep(1);}return 0;
}
线程终止
在进程中我们可以调用 exit()
函数或 _exit()
函数(详细参考:[[09_Linux 进程基础#3、进程退出|进程退出]])来结束进程,类似的,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。
- 线程从执行函数中返回。
- 线程调用 pthread_exit 退出线程——线程终止。
- 线程可以被同一进程中的其它线程取消。
#include <pthread.h>void pthread_exit(void *retval);
功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。参数:retval:存储线程退出状态的指针。
返回值:无
【补充】主线程退出其他线程不退出,主线程应调用 pthread_exit 来退出,这样主线程退出后不会影响到子线程的运行。
参考程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>void *thread(void *arg) {static int num = 123; //静态变量int i = 0;while (1) {printf("I am runing\n");sleep(1);i++;if (i == 5) {pthread_exit((void *)&num); // 相当于 return #// pthread_exit(NULL); // 相当于 return NULL;}}return NULL;
}int main(int argc, char *argv[]) {int ret = 0;pthread_t tid;void *value = NULL;pthread_create(&tid, NULL, thread, NULL);pthread_join(tid, &value);printf("value = %d\n", *(int *)value);return 0;
}
线程取消
在进程中我们可以调用 exit()
函数或 _exit()
函数(详细参考:[[09_Linux 进程基础#3、进程退出|进程退出]])来结束进程,类似的,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。
- 线程从执行函数中返回。
- 线程调用 pthread_exit 退出线程——线程终止。
- 线程可以被同一进程中的其它线程取消——线程取消。
#include <pthread.h>int pthread_cancel(pthread_t thread);
功能:杀死(取消)线程
参数:thread : 目标线程ID。
返回值:成功:0失败:出错编号
【注意】
- 线程的取消(杀死线程)并不是实时的,而是有一定的延时。需要等待线程到达某个取消点(也叫检查点)。类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。
- 取消点:线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write… 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表。
- 可粗略认为一个系统调用(进入内核)即为一个取消点。
参考程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_cancel(void *arg) {while (1) {sleep(1);printf("The child thread still exists\n");}return NULL;
}int main() {pthread_t tid = -1;pthread_create(&tid, NULL, thread_cancel, NULL); //创建线程sleep(3); //3秒后pthread_cancel(tid); //取消tid线程:主线程杀死tid线程printf("The child thread already over\n");pthread_join(tid, NULL);return 0;
}
线程属性
概述
linux 下线程的属性是可以根据实际项目需要进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。
但如果我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。
线程属性值不能直接设置,须使用相关函数进行操作,初始化的函数为 pthread_attr_init,这个函数必须在 pthread_create 函数之前调用。之后须用 pthread_attr_destroy 函数来释放资源。
线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。这些属性都使用结构体来体现——线程属性类型:pthread_attr_t,结构体详情如下:
typedef struct
{int etachstate; //线程的分离状态int schedpolicy; //线程调度策略struct sched_param schedparam; //线程的调度参数int inheritsched; //线程的继承性int scope; //线程的作用域size_t guardsize; //线程栈末尾的警戒缓冲区大小int stackaddr_set; //线程的栈设置void* stackaddr; //线程栈的位置size_t stacksize; //线程栈的大小
} pthread_attr_t;
结构体主要成员:
- 线程分离状态
- 线程栈大小(默认平均分配)
- 线程栈警戒缓冲区大小(位于栈末尾)
- 线程栈最低地址
属性初始化和销毁
#include <pthread.h>int pthread_attr_init(pthread_attr_t *attr);
功能:初始化线程属性函数,注意:应先初始化线程属性,再 pthread_create 创建线程
参数:attr:线程属性结构体
返回值:成功:0失败:错误号int pthread_attr_destroy(pthread_attr_t *attr);
功能:销毁线程属性所占用的资源函数
参数:attr:线程属性结构体
返回值:成功:0失败:错误号
分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。
- 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当
pthread_join()
函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。 - 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
#include <pthread.h>int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:设置线程分离状态
参数:attr:已初始化的线程属性detachstate: 分离状态PTHREAD_CREATE_DETACHED(分离线程)PTHREAD_CREATE_JOINABLE(非分离线程)
返回值:成功:0失败:非0int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能:获取线程分离状态
参数:attr:已初始化的线程属性detachstate: 分离状态PTHREAD_CREATE_DETACHED(分离线程)PTHREAD _CREATE_JOINABLE(非分离线程)
返回值:成功:0失败:非0
【注意】
- 如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在线程函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用 pthread_create 创建的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread_cond_timedwait 函数,让这个线程等待一会儿,留出足够的时间让函数 pthread_create 返回。
- 设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如 wait() 之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_cancel(void *arg) {while (1) {sleep(1);printf("The child thread still exists\n");}return NULL;
}int main() {pthread_t tid = -1;int ret = -1;pthread_attr_t attr;ret = pthread_attr_init(&attr);if (0 != ret) {printf("pthread_att_init failed...\n");return 1;}ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);if (0 != ret) {printf("pthread_attr_setdetachstate failed...\n");return 1;}// 创建一个线程,使用初始化好的属性ret = pthread_create(&tid, &attr, thread_cancel, NULL); //创建线程if (0 != ret) {printf("pthread_create failed...\n");return 1;}ret = pthread_attr_destroy(&attr);if (0 != ret) {printf("pthread_attr_destroy failed...\n");return 1;}return 0;
}
线程栈地址
POSIX.1 定义了两个常量来检测系统是否支持栈属性:
_POSIX_THREAD_ATTR_STACKADDR
_POSIX_THREAD_ATTR_STACKSIZE
也可以给 sysconf 函数传递来进行检测:_SC_THREAD_ATTR_STACKADDR
_SC_THREAD_ATTR_STACKSIZE
当进程栈地址空间不够用时,指定新建线程使用由 malloc 分配的空间作为自己的栈空间;通过 pthread_attr_setstack 和 pthread_attr_getstack 两个函数分别设置和获取线程的栈地址。
#include <pthread.h>int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
功能:设置线程的栈地址
参数:attr:指向一个线程属性的指针stackaddr:内存首地址stacksize:返回线程的堆栈大小
返回值:成功:0失败:错误号int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
功能:获取线程的栈地址
参数:attr:指向一个线程属性的指针stackaddr:返回获取的栈地址stacksize:返回获取的栈大小
返回值:成功:0失败:错误号
线程栈大小
当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。
#include <pthread.h>int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
功能:设置线程的栈大小
参数:attr:指向一个线程属性的指针stacksize:线程的堆栈大小
返回值:成功:0失败:错误号int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
功能:获取线程的栈大小
参数: attr:指向一个线程属性的指针stacksize:返回线程的堆栈大小
返回值:成功:0失败:错误号
综合参考程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>#define SIZE 0x100000void *th_fun(void *arg) {while (1) {sleep(1);}
}int main() {pthread_t tid;int err, detachstate, i = 1;pthread_attr_t attr;size_t stacksize;void *stackaddr;pthread_attr_init(&attr); //线程属性初始化pthread_attr_getstack(&attr, &stackaddr, &stacksize); //获取线程的栈地址pthread_attr_getdetachstate(&attr, &detachstate); //获取线程分离状态if (detachstate == PTHREAD_CREATE_DETACHED) {printf("thread detached\n");}else if (detachstate == PTHREAD_CREATE_JOINABLE) {printf("thread join\n");}else {printf("thread unknown\n");}pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置分离状态while (1) {stackaddr = malloc(SIZE);if (stackaddr == NULL) {perror("malloc");exit(1);}stacksize = SIZE;pthread_attr_setstack(&attr, stackaddr, stacksize); //设置线程的栈地址err = pthread_create(&tid, &attr, th_fun, NULL); //创建线程if (err != 0) {printf("%s\n", strerror(err));exit(1);}printf("%d\n", i++);}pthread_attr_destroy(&attr); //销毁线程属性所占用的资源函数return 0;
}
线程使用注意事项
- 主线程退出其他线程不退出,主线程应调用 pthread_exit 来退出,这样主线程退出后不会影响到子线程的运行。
- 避免僵尸线程
- pthread_join
- pthread_detach
- pthread_create 指定分离属性,被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
- malloc 和 mmap 申请的内存可以被其他线程释放
- 应避免在多线程模型中调用 fork,除非马上 exec,子进程中只有调用 fork的线程存在,其他线程在子进程中均 pthread_exit
- 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制
- 局部变量是一个栈空间的数据,当一个线程结束以后,会释放内存空间,再去获取该内存的值就是一个随机值。所以线程退出的时候一定不要返回一个局部变量。