alin的学习之路(Linux系统编程:九)(SIGCHLD、守护进程、线程)
1. 可重入与不可重入函数
- 不可重入函数:如果有一个函数不幸被设计成为这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。
满足下列条件的函数多数是不可重入(不安全)的:
- 函数体内使用了静态的数据结构;
- 函数体内调用了malloc() 或者 free() 函数(谨慎使用堆);
- 函数体内调用了标准 I/O 函数。
- 可重入函数:所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。
保证函数的可重入性的方法:
- 在写函数时候尽量使用局部变量(例如寄存器、栈中的变量);
- 对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。
注意:信号处理函数应该为可重入函数。
2. SIGCHLD 与 wait
1.SIGCHLD 产生的条件
-
子进程终止时
-
子进程接收到SIGSTOP信号停止时
-
子进程处在停止态,接受到SIGCONT后唤醒时
2.wait与waitpid的执行条件
当子进程结束时,会给父进程发送一个 SIGCHLD 的信号,当父进程收到这个信号时,父进程中的 wait 就会调用。
那么wait函数和waitpid函数执行的条件是:父进程收到了 SIGCHLD 或 设置了非阻塞会直接执行。
3.父进程通过信号处理函数回收子进程
当父进程有其他工作需要做的时候,不方便阻塞等待回收子进程,那么就可以使用信号处理函数的方式进行子进程的回收
注册一个收到SIGCHLD信号后会响应的函数,在函数中使用wait或waitpid
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
void func(int signo,siginfo_t* info,void* context )
{
printf("捕捉到子进程结束信号:%d\n",signo);
while(1)
{
int ret = waitpid(-1,NULL,0);
if(-1 == ret)
{
perror("waitpid");
break;
}
printf("回收了子进程:%d\n",ret);
}
}
int main()
{
struct sigaction act = {
.sa_sigaction = func,
.sa_flags = SA_SIGINFO
};
int num = sigaction(SIGCHLD,&act,NULL);
if(-1 == num)
{
perror("sigaction");
return 1;
}
pid_t pid;
pid = fork();
if(-1 == pid)
{
perror("fork");
return 1;
}
else if(0 == pid)
{
for(int i=0 ;i<3 ;++i)
{
printf("child do work %d\n",i);
sleep(1);
}
exit(0);
}
else
{
for(int i=0 ;i<6 ;++i)
{
printf("father do work %d\n",i);
sleep(1);
}
}
return 0;
}
如果父进程不打算回收子进程,而是想要等到父进程执行结束后让内核去处理回收资源,那么可以在注册信号处理函数时写上SIG_IGN
3. 终端的概念
在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal),进程中,控制终端是保存在PCB中的信息,而fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。
默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。
信号中还讲过,在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl+C表示SIGINT,Ctrl+\表示SIGQUIT。
使用 ttyname函数可以查看当前终端
#include <unistd.h>
char *ttyname(int fd);
功能:由文件描述符查出对应的文件名
参数:
fd:文件描述符
返回值:
成功:终端名
失败:NULL
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("STDIN_FILENO : %s\n",ttyname(STDIN_FILENO));
printf("STDOUT_FILENO : %s\n",ttyname(STDOUT_FILENO));
printf("STDERR_FILENO : %s\n",ttyname(STDERR_FILENO));
return 0;
}
4. 进程组和会话
1. 进程组
代表一个或多个进程的集合。
每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。
当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID为第一个进程ID(组长进程)。所以,组长进程标识:其进程组ID为其进程ID
可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死
进程组的生命周期:从创建出来开始,直到其中的最后一个进程结束。进程组长的存亡与进程组的存亡没有直接的关系。
#include <unistd.h>
pid_t getpgrp(void); /* POSIX.1 version */
功能:获取当前进程的进程组ID
参数:无
返回值:总是返回调用者的进程组ID
pid_t getpgid(pid_t pid);
功能:获取指定进程的进程组ID
参数:
pid:进程号,如果pid = 0,那么该函数作用和getpgrp一样
返回值:
成功:进程组ID
失败:-1
int setpgid(pid_t pid, pid_t pgid);
功能:
改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。
参数:
将参1对应的进程,加入参2对应的进程组中
返回值:
成功:0
失败:-1
2. 会话
会话是一个或多个进程组的集合。
- 一个会话可以有一个控制终端。这通常是终端设备或伪终端设备;
- 建立与控制终端连接的会话首进程被称为控制进程;
- 一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组;
- 如果一个会话有一个控制终端,则它有一个前台进程组,其它进程组为后台进程组;
- 如果终端接口检测到断开连接,则将挂断信号发送至控制进程(会话首进程)。
如果一个进程创建在一个终端并且将它挂到后台,当这个终端关闭时,这个进程也一同被杀死。
创建会话注意事项:
-
调用进程不能是进程组组长,该进程变成新会话首进程(session header)
-
该调用进程是组长进程,则出错返回
-
该进程成为一个新进程组的组长进程
-
需有root权限(ubuntu不需要)
-
新会话丢弃原有的控制终端,该会话没有控制终端
-
建立新会话时,先调用fork, 父进程终止,子进程调用setsid
相关函数介绍:
getsid函数:
#include <unistd.h>
pid_t getsid(pid_t pid);
功能:获取进程所属的会话ID
参数:
pid:进程号,pid为0表示查看当前进程session ID
返回值:
成功:返回调用进程的会话ID
失败:-1
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
setsid函数:
#include <unistd.h>
pid_t setsid(void);
功能:
创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。调用了setsid函数的进程,既是新的会长,也是新的组长。
参数:无
返回值:
成功:返回调用进程的会话ID
失败:-1
5. 守护进程
守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。
守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。
创建一个守护进程有6个步骤:
- 创建子进程,父进程退出(必须)
- 所有工作在子进程中进行形式上脱离了控制终端
- 在子进程中创建新会话(必须)
- setsid()函数
- 使子进程完全独立出来,脱离控制
- 改变当前目录为根目录(不是必须)
- chdir()函数
- 防止占用可卸载的文件系统
- 也可以换成其它路径
- 重设文件权限掩码(不是必须)
- umask()函数
- 防止继承的文件创建屏蔽字拒绝某些权限
- 增加守护进程灵活性
- 关闭文件描述符(不是必须)
- 继承的打开文件不会用到,浪费系统资源,无法卸载
- 开始执行守护进程核心工作(必须)
守护进程退出处理程序模型
其中umask 的执行原理:
默认文件创建的最大权限是0666,即110 110 110
默认umask 为 002 ,即 000 000 010
首先将umask取反 ~umask = 111 111 101
然后再和默认文件创建最大权限位与得出创建出的文件权限:111 111 101 & 110 110 110 = 110 110 100
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
pid_t pid;
//1.创建子进程,父进程退出
pid = fork();
if(-1 == pid)
{
perror("fork");
return 1;
}
else if(pid > 0)
{
//父进程友好退出
exit(0);
}
//子进程
//2.创建会话
int ret = setsid();
if(-1 == ret)
{
perror("setsid");
return 1;
}
printf("new session id = %d\n",ret);
//改变工作目录到根目录
ret = chdir("/");
if(-1 == ret)
{
perror("chdir");
return 1;
}
//改变umask为0
umask(0);
//关闭不必要地文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
//周期性执行工作
while(1)
{
system("echo \"hello itcast\" >> /home/itcast/classcode/9day/txt.log");
sleep(1);
}
return 0;
}
上述代码每隔一秒将“hello itcast”写入文件中,使用 tail -f 文件路径
可以动态查看文件末尾
创建守护进程还有一个函数可以使用
int daemon(int nochdir, int noclose);
功能:
创建一个守护进程
参数:
nochdir
0 改变当前进程的工作目录到根目录
其它 当前进程工作目录不会变化
noclose
0 重定向标准输入 标准输出 标准错误输出到/dev/null
其它 不重定向描述符
返回值:
成功 0
失败 -1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
//1.使用daemon函数来创建一个守护进程
int ret = daemon(0,0);
if(-1 == ret)
{
perror("daemon");
return 1;
}
//2.周期性执行工作
while(1)
{
system("echo \"hello world\" >> /home/itcast/classcode/9day/txt.log");
sleep(1);
}
return 0;
}
6. 线程
1.综述
线程是轻量级的进程.
进程是CPU分配资源的最小单位。
线程存在与进程当中(进程可以认为是线程的容器),是操作系统调度执行的最小单位。说通俗点,线程就是干活的。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
总述:
进程是操作系统分配资源的最小单位
线程是操作系统调度的最小单位
2. 线程函数列表安装
默认系统如果没有线程函数的manpage需要安装
命令:
sudo apt-get install manpages-posix-dev
【说明】manpages-posix-dev 包含 POSIX 的 header files 和 library calls 的用法
查看:
man -k pthread
3. 线程的特点
进程和线程关系密切:
-
线程是轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
-
从内核里看进程和线程是一样的,都有各自不同的PCB.
-
进程可以蜕变成线程
-
在linux下,线程最是小的执行单位;进程是最小的分配资源单位
实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数 clone 。
Ø 如果复制对方的地址空间,那么就产出一个“进程”;
Ø 如果共享对方的地址空间,就产生一个“线程”。
Linux内核是不区分进程和线程的, 只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。
4. 线程共享资源
-
文件描述符表
-
每种信号的处理方式
-
当前工作目录
-
用户ID和组ID
-
内存地址空间 (.text/.data/.bss/heap/共享库)
5. 线程非共享资源
-
线程id
-
处理器现场和栈指针(内核栈)
-
独立的栈空间(用户空间栈)
-
errno变量
-
信号屏蔽字
-
调度优先级
6. 线程的优缺点
优点:
Ø 提高程序并发性
Ø 开销小
Ø 数据通信、共享数据方便
缺点:
Ø 库函数,不稳定
Ø 调试、编写困难、gdb不支持
Ø 对信号支持不好
优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。
7. 线程号
每个线程都有一个线程号,并且该线程号只在该进程中有效。
线程号使用 pthread_t 类型表示,Linux使用无符号长整型表示。
注意:有些系统使用结构体来表示,所以在可移植的程序上不能把它当作一个整数。
pthread_self函数:
#include <pthread.h>
pthread_t pthread_self(void);
功能:
获取线程号。
参数:
无
返回值:
调用线程的线程 ID 。
pthread_equal函数:
int pthread_equal(pthread_t t1, pthread_t t2);
功能:
判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
参数:
t1,t2:待判断的线程号。
返回值:
相等: 非 0
不相等:0
示例代码:
#include <stdio.h>
#include <pthread.h>
int main()
{
//注意tid的类型是无符号长整形 %lu
pthread_t tid;
tid = pthread_self();
printf("tid = %lu\n",tid);
if(pthread_equal(tid,pthread_self()) != 0)
{
printf("tid与pthread_self()的返回值相等\n");
}
else
{
printf("不相等\n");
}
return 0;
}
8. 创建线程
pthread_create函数
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg );
功能:
创建一个线程。
参数:
thread:线程标识符地址。
attr:线程属性结构体地址,通常设置为 NULL。
start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值:
成功:0
失败:非 0
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。
由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信息,可以先用**strerror()**把错误码转换成错误信息再打印。
strerror()头文件是 #include <string.h>
示例代码1:(不传参)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void* func(void *arg)
{
for(int i=0 ;i<3 ;++i)
{
printf("child do work %d\n",i);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,func,NULL);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
for(int i=0 ;i<5 ;++i)
{
printf("main do work %d\n",i);
sleep(1);
}
return 0;
}
示例代码2:(传参)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* func(void *arg)
{
printf("tid = %lu\n",pthread_self());
printf("arg = %#lx\n",(long)arg);
return NULL;
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,func,(void*)0x33);
if(0 != ret)
{
printf("%s\n",strerror(ret));
}
sleep(10);
return 0;
}
需要注意的是:当主线程执行到return 0
的时候,整个进程会终止,其所有的线程也就终止了。
避免这个情况:使用 pthread_exit()
在下文介绍
9. 线程的回收
pthread_join函数:
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址。
返回值:
成功:0
失败:非 0
需要注意:返回值之间的类型转换
示例代码:
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void* func(void *arg)
{
static int num = 100;
return (void*) 100;
//return #
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,func,NULL);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
void* val = NULL;
ret = pthread_join(tid,&val);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
printf("主线程回收了子线程并得到返回值:%d\n",val);
return 0;
}
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
-
如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
-
如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
-
如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
9. 线程分离
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。
pthread_detach函数:
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
参数:
thread:线程号。
返回值:
成功:0
失败:非0
示例代码:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void* func(void *arg)
{
for(int i=0 ;i<5 ;++i)
{
printf("child do work %d\n",i);
sleep(1);
}
return (void*)0x33;
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,func,NULL);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
ret = pthread_detach(tid);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
void* val;
ret = pthread_join(tid,&val);
if(0 != ret)
{
printf("%s\n",strerror(ret));
}
for(int i=0 ;i<3 ;++i)
{
printf("main do work %d\n",i);
sleep(1);
}
pthread_exit(0);
}
10. 线程退出
在进程中我们可以调用exit函数或_exit函数来结束进程,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。
- 线程从执行函数中返回。
- 线程调用pthread_exit退出线程。
- 线程可以被同一进程中的其它线程取消。
pthread_exit函数:
#include <pthread.h>
void pthread_exit(void *retval);
功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
参数:
retval:存储线程退出状态的指针。
返回值:无
示例代码:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void* func(void* arg)
{
static int num = 0;
for(int i=0 ; i<5; ++i)
{
++num;
if(2 == i)
pthread_exit((void*)&num);
}
return NULL;
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,func,NULL);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
void* val = NULL;
ret = pthread_join(tid,&val);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
printf("child return %d\n",*(int*)val);
return 0;
}
11. 线程取消
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
杀死(取消)线程
参数:
thread : 目标线程ID。
返回值:
成功:0
失败:出错编号
设置取消点:pthread_testcancel
注意:线程的取消并不是实时的,而又一定的延时。需要等待线程到达某个取消点(检查点)。
类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。
杀死线程也不是立刻就能完成,必须要到达取消点。
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write… 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。
可粗略认为一个系统调用(进入内核)即为一个取消点。
示例代码:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void* func(void* arg)
{
for(int i=0 ; i<5; ++i)
{
pthread_testcancel();
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,func,NULL);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
sleep(2);
ret = pthread_cancel(tid);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
else
{
printf("线程:%lu已被取消\n",tid);
}
return 0;
}
12. 线程的属性
线程的属性在底层用一个结构体来表示,类型为: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;
主要结构体成员:
-
线程分离状态
-
线程栈大小(默认平均分配)
-
线程栈警戒缓冲区大小(位于栈末尾)
-
线程栈最低地址
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。
线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
12.1 线程属性的初始化和销毁
#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
失败:错误号
12.2 线程的分离
线程的分离状态决定一个线程以什么样的方式来终止自己。
- 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
- 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:设置线程分离状态
参数:
attr:已初始化的线程属性
detachstate: 分离状态
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD_CREATE_JOINABLE(非分离线程)
返回值:
成功:0
失败:非0
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能:获取线程分离状态
参数:
attr:已初始化的线程属性
detachstate: 分离状态
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD _CREATE_JOINABLE(非分离线程)
返回值:
成功:0
失败:非0
这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。
要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。
设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
12.3 线程栈大小
当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。
#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
失败:错误号
12.4 示例代码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void* func(void *arg)
{
//pthread_cond_timedwait
sleep(1);
return NULL;
}
int main()
{
pthread_t tid;
pthread_attr_t attr;
int ret = pthread_attr_init(&attr);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
int detachstate;
ret = pthread_attr_getdetachstate(&attr,&detachstate);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
if(detachstate == PTHREAD_CREATE_DETACHED)
printf("分离状态\n");
else if(detachstate == PTHREAD_CREATE_JOINABLE)
printf("可join状态\n");
else
printf("未找到pthread");
ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
ret = pthread_attr_getdetachstate(&attr,&detachstate);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
if(detachstate == PTHREAD_CREATE_DETACHED)
printf("分离状态\n");
else if(detachstate == PTHREAD_CREATE_JOINABLE)
printf("可join状态\n");
else
printf("未找到pthread");
ret = pthread_create(&tid,&attr,func,NULL);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
ret = pthread_join(tid,NULL);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
ret = pthread_attr_destroy(&attr);
if(0 != ret)
{
printf("%s\n",strerror(ret));
return 1;
}
return 0;
}