C++笔记--Linux编程(13)-守护进程-线程
Posted xiangjai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++笔记--Linux编程(13)-守护进程-线程相关的知识,希望对你有一定的参考价值。
目录
进程组
概念和特性
每个进程都属于一个进程组,在waitpid函数和kill函数的参数中都曾使用到。
当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID==第一个进程ID(组长继承)。所有,组长进程表示,其进程组ID==其进程ID
可以使用kill-SIGKILL-进程组ID来将整个进程组内的进程全部杀死
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程村庄,进程组就存在,与组长进程是否终止无关。
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)
一个进程可以为自己或子进程设置进程组ID
进程组操作函数(了解)
getpgrp函数
会话
创建会话
创建一个会话需要注意一下事项:
1. 调用进程不能是进程组组长,该进程变成新会话首进程(session header)
2. 该进程成为一个新进程组的组长进程
3. 新会话丢弃原有的控制终端,该会话没有控制终端
4. 该调用进程是组长进程,则出错返回
5. 建立新会话时,先调用fork,父进程终止,子进程调用setsid
getsid函数
获取进程所属的会话ID
pid_t getsid(pid_t pid); 成功,返回调用进程的会话ID;失败 -1,设置errno
pid为0表示查看当前进程session ID
ps ajx命令查看系统中的进程,参数a表示不仅当前用户的进程,也列出所有其它用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。
pid_t setsid()
setsid函数创建一个新会话和一个新进程组,然后守护进程成为新会话的会话领导,以及新进程组的进程组领导。
setsid调用还保证新会话没有控制终端。 如果调用进程已经是一个进程组的领导进程,setsid调用失败。
setsid调用成功返回新会话ID,失败返回-1,并设置errno。
会话和进程总结
会话:进程组的更高一级,多个进程组对应一个会话
进程组:多个进程在同一个组,第一个进程默认是进程组的组长
创建会话的时候,组长不可创建,必须是组员创建
创建会话的步骤:创建子进程,父进程终止,子进程自己当会长
守护进程
创建守护进程模型
1. 创建子进程,父进程退出
所有工作在子进程中进行形式上脱离了控制终端
2. 在子进程中创建新会话
setsid()函数
使子进程完全独立出来,脱离控制
3. 改变当前目录为根目录
chdir()函数
防止占用可卸载的文件系统,也可以换成其它路径
4. 重设文件权限掩码
umask()函数
防止继承的文件创建屏蔽拒绝某些权限,增加守护进程灵活性,
5. 关闭文件描述符
继承的打开文件不会用到,浪费系统资源,无法卸载
6. 开始执行守护进程核心工作
7. 守护进程退出处理程序模型
守护进程的步骤:
1. 创建子进程fork
2. 父进程退出
3. 子进程当会长 setsid
4. 切换工作目录 $HOME
5. 设置掩码 umask
6. 关闭文件描述符0,1,2 ,为了避免浪费资源
7. 执行核心逻辑
8. 退出
进程与线程
进程是一个实体。每一个进程都有它自己的内存地址段(heap、stack等等)
进程是执行中的程序。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体。
进程是操作系统中最基本、重要的概念。
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行的最小单元。
每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
单线程的进程可以简单的认为只有一个线程的进程。
一个进程在同一时间只做一件事,有了多线程后一个进程同一时间可以做多件事。
每个线程可以处理不同的事务。 无论系统有几个CPU,即使进程运行在单CPU上,多线程也可以使进程并发处理多个事务。
一个线程阻塞并不会影响到另外一个线程。 多线程的进程可以尽可能的利用系统CPU资源。
线程包含了表示进程内执行环境必须的信息,包括标识线程的线程ID,一组寄存器值,栈,调度优先级和策略,信号屏蔽字,errno变量以及线程私有数据。
进程内所有的信息对于线程都是共享的,包括执行代码,全局变量,和堆内存,栈以及文件描述符。
线程标识
就像每个进程有个进程ID一样,线程也有自己的ID。
进程ID用pid_t来表示,它是一个unsigned int。
线程ID用pthread_t表示,pthread_t不能把它当整数处理。
线程可以通过pthread_self()函数获得自身的线程ID
ps -Lf pid查看置顶现场的lwp好
线程调用
线程创建
在进程中只有一个控制线程 程序开始运行的时候每个进程只有一个线程,它是以单线程方式启动的,在创建多个线程以前,进程的行为与传统的进程没有区别。
gcc在链接的时候需要增加-lpthread选项。 创建一个线程调用pthread_create函数。
#include <pthread.h>
int pthread_create(pthread_t *thread,const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
如果pthread_create成功返回,由thread指向的内存单元被设置为新创建线程的线程ID。
attr参数用于定制各种不同的线程属性。
新创建的线程从start_rtn函数地址开始执行,该函数只有一个void *参数,如果需要向start_rtn函数传递多个参数,就需要把这些参数放到一个结构中,然后把这个结构的地址做为void *传入。
线程创建的时候不能保证哪个先运行。 pthread函数成功返回0。
注意:每个线程都拥有一份errno副本,不同的线程拥有不同的errno
pthread_create示例
void *func(void *arg)
{
printf("pthread start\\n");
return NULL;
}
int main(int arg, char * args[])
{
pthread_t thr_d;
int err = pthread_create(&thr_d, NULL, func, NULL);
if (err != 0)
{
printf("create pthread failed\\n");
}
else
{
printf("create pthread success\\n");
}
sleep(1);
return 0;
}
注:gcc链接时需要加-lpthread选项
线程终止
任一线程调用了exit函数,整个进程就会终止。
如果信号默认动作是终止进程,那么信号发送到该线程,整个进程也会被终止
单个线程通过以下三种方式退出
1. 线程只是从启动函数中返回,返回值是线程的退出码。
2. 线程可以被同一进程中的其他线程取消。
3. 线程调用pthread_exit
线程退出函数
void pthread_exit(void *arg);
arg是个无类型指针,该指针会被其他线程调用pthread_join捕捉。
线程退出示例
void *func(void *arg)
{
printf("pthread start\\n");
static int i = 1;
pthread_exit(&i);
}
int main(int arg, char * args[])
{
pthread_t thr_d;
pthread_create(&thr_d, NULL, func, NULL);
sleep(1);
return 0;
}
线程退出注意事项
1. 线程中使用pthread_exit
2. 线程中使用return(主控线程return代表退出线程)
3. exit代表退出整个进程
线程回收
线程回收函数
int pthread_join(pthread_t th, void **thr_return);。
pthread_join函数用于挂起当前线程,直至th指定的线程终止为止。
如果另一个线程返回值不是NULL,则保存在thr_return地址中。
一个线程所使用的内存资源在应用pthread_join调用之前不会被重新分配,所以对于每个线程必须调用一次pthread_join函数。
其他线程不能对同一线程再应用pthread_join调用。
线程回收示例
void *func(void *arg)
{
printf("pthread start\\n");
static int i = 0;
sleep(2);
pthread_exit(&i);
}
int main(int arg, char * args[])
{
pthread_t thr_d;
pthread_create(&thr_d, NULL, func, NULL);
void *p;
pthread_join(thr_d, &p);
int *pi = (int *)p;
printf("thread exit %d\\n", *pi);
return 0;
}
杀死线程 (取消线程)
杀死线程(取消线程)函数
int pthread_cancel(pthread_t th);
pthread_cancel函数允许一个线程取消th指定的另一个线程。 函数成功,返回0,否则返回非0。
线程分离
线程分离函数
int pthread_detach(pthread_t th);
pthread_detach函数使线程处于被分离状态。
对于被分离状态的线程,不需要调用pthread_join,如果其他线程调用pthread_join失败,返回EINVAL。
如果不等待一个线程,同时对线程的返回值不感兴趣,可以设置这个线程为被分离状态,让系统在线程退出的时候自动回收它所占用的资源。
一个线程不能自己调用pthread_detach改变自己为被分离状态,只能由其他线程调用pthread_detach。
线程分离示例
void *func(void *arg)
{
printf("pthread start\\n");
pthread_exit(NULL);
}
int main(int arg, char * args[])
{
pthread_t thr_d;
pthread_create(&thr_d, NULL, func, NULL);
pthread_detach(thr_d);
sleep(1);
return 0;
}
线程比较
线程比较函数
int pthread_equal(pthread_t th1,pthread_t th2);
pthread_equal函数比较th1与th2是否为同一个线程,由于不可以将pthread数据类型认为是整数,所以也不能用比较整数的方式比较pthread_t。
如果th1与th2相同,函数返回非0值,如果不同函数返回0。
线程属性
以前调用pthread_create传入的attr参数都是空指针,而不是指向pthread_attr_t结构的指针。
可以使用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来
可以使用pthread_attr_init函数初始化pthread_attr_t结构。
调用pthread_attr_init以后,pthread_arrt_t的结构所包含的内容就是操作系统实现支持线程所有属性的默认值。如果要修改其中个别属性的值,需要调用其他函数。
线程使用注意事项
1. 主线程退出其它线程不退出,主线程应调用pthread_exit
2. 避免僵尸线程
pthread_join
pthread_detach
pthread_create
被join线程可能在join函数返回前释放完自己所有的内存资源,所以不应当返回被回收线程栈中的值
3. malloc和mmap申请的内存可以被其它线程释放
4. 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其它线程在子进程中均pthread_exit
5. 信号的复杂意义很难喝多线程共存,应避免在多线程引入信号机制
以上是关于C++笔记--Linux编程(13)-守护进程-线程的主要内容,如果未能解决你的问题,请参考以下文章