C++笔记--Linux编程(13)-守护进程-线程

Posted xiangjai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++笔记--Linux编程(13)-守护进程-线程相关的知识,希望对你有一定的参考价值。

目录

进程组

 概念和特性

进程组操作函数(了解)

会话

创建会话

getsid函数

pid_t setsid()

会话和进程总结

守护进程

创建守护进程模型

进程与线程

线程标识

线程调用

线程创建

pthread_create示例

线程终止

单个线程通过以下三种方式退出 

线程退出函数

线程退出示例

线程退出注意事项

线程回收

线程回收函数

线程回收示例

杀死线程 (取消线程)

杀死线程(取消线程)函数

线程分离

线程分离函数

线程分离示例

线程比较

线程比较函数

线程属性

线程使用注意事项


进程组

 概念和特性

         每个进程都属于一个进程组,在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)-守护进程-线程的主要内容,如果未能解决你的问题,请参考以下文章

Android C++系列:Linux守护进程

C++笔记--Linux编程(10)-进程控制 fork系统调用

C++笔记--Linux编程(11)-进程通信

linux 如何实现java守护进程编程开发

Linux编程之《守护进程》

[C++]-Linux中创建Daemon程序