C++linux高并发服务器项目实践 day10
守护进程
在UNIX系统中,用户通过终端登录系统后得到一个shell进程,这个终端称为shell进程的控制终端(Controlling Terminal),进程中,控制终端是保存在PCB中的信息,而fork()会复制PCB中的信息,因此由shell进程启动的其他进程的控制终端也是这个终端
默认情况下(没有重定向),每个进程的标准输入、输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上
在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl + C会产生SIGINT信号,Ctrl + \会产生SIGQUIT信号
echo $$
可以通过上述命令来查看当前终端的进程号
tty
可以用于显示终端机连接标准输入设备的文件名称
进程组
进程组和会话在进程之间形成了一种两级层次关系:进程组是一组相关进程的集合,会话是一组相关进程组的集合。进程组和会话是为支持shell作业控制而定义的抽象概念,用户通过shell能够交互式地在前台或后台运行命令
进程组由一个或多个共享同一进程组标识符(PGID)的进程组成。一个进程组拥有一个进程组首进程,该进程是创建该组的进程,其进程ID为该进程组的ID,新进程会继承其父进程所属的进程组ID
进程组拥有一个生命周期,其开始时间为首进程创建组的时刻,结束时间为最后一个成员进程退出组的时刻,一个进程可能会因为终止而退出进程组,也可能会因为加入了另外一个进程组而退出进程组。进程组首进程无需是最后一个离开进程组的成员
会话
会话是一组进程组的集合。会话首进程是创建该新会话的进程,其进程ID会成为会话ID。新进程会继承其父进程的会话ID
一个会话中的所有进程共享单个控制终端。控制终端会在会话首进程首次打开一个终端设备时被建立,一个终端最多可能会成为一个会话的控制终端
在任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其他进程组会成为后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。当用户在控制终端中输入终端字符生成信号后,该信号会被发送到前台进程组中的所有成员
当控制终端的连接建立起来之后,会话首进程会成为该终端的控制进程
进程组、会话操作函数
- pid_t getpgrp(void); 获取当前进程的进程组
- pid_t getpgid(pid_t pid);获取指定进程的进程组id
- int setpgid(pid_t pid ,pid_t pgid);设置进程组的ID
- pid_t getsid(pid_t pid);获取指定进程的会话ID
- pid_t setsid(void);设置会话的ID
守护进程
守护进程,也就是通常说的Daemon进程(精灵进程),是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字
守护进程具备下列特征:
- 生命周期很长,守护进程会在系统启动的时候被创建并一直运行直至系统被关闭
- 它在后台运行并且不拥有控制终端。没有控制终端确保了内核永远不会为守护进程自动生成任何控制信号以及终端相关的信号(如SIGINT、SIGQUIT).
Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等
守护进程的创建步骤
- 执行一个fork(),之后父进程退出,子进程继续执行
- 子进程调用setsid()开启一个新会话
- 清楚进程的umask以确保当守护进程创建文件和目录时拥有所需的权限
- 修改进程的当前工作目录,通常会改为根目录(/)
- 关闭守护进程从其父进程继承而来的所有打开着的文件描述符
- 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null并使用dup2()使所有这些描述符指向这个设备
- 核心业务逻辑
/*
写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/time.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
void work(int num){
//捕捉到信号之后,获取系统时间,写入磁盘文件
time_t tm = time(NULL);
struct tm * loc = localtime(&tm);
//方法一
// char buf[1024];
// sprintf(buf,"%d-%d-%d %d:%d:%d\n",loc->tm_year,loc->tm_mon,loc->tm_mday,loc->tm_hour,loc->tm_min,loc->tm_sec);
// printf("%s\n",buf);
//方法二
char * str = asctime(loc);
int fd = open("time.txt",O_RDWR | O_CREAT|O_APPEND,0664);
write(fd,str,strlen(str));
close(fd);
}
int main(){
//1.创建子进程,退出父进程
pid_t pid = fork();
if(pid > 0){
exit(0);
}
//2.将子进程重新创建一个会话
setsid();
//3.设置掩码
umask(022);
//4.更改工作目录
chdir("/home/yuuki/lesson/lesson28/");
//5.关闭、重定向文件描述符
// int fd = open("/dev/null",O_RDWR);
// dup2(fd,STDIN_FILENO);
// dup2(fd,STDOUT_FILENO);
// dup2(fd,STDERR_FILENO);
//6.业务逻辑
//捕捉定时信号
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = work;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM,&act,NULL);
struct itimerval val;
val.it_interval.tv_sec = 2;
val.it_interval.tv_usec = 0;
val.it_value.tv_sec = 2;
val.it_value.tv_usec = 0;
//创建定时器
setitimer(ITIMER_REAL,&val,NULL);
while(1){}
return 0;
}
输出的结果可以在time.txt中查看,vim里可以使用下面代码来刷新文本
:e
线程
与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内部区域,其中包括初始化数据段、未初始化数据段,以及堆内存段(传统意义上的UNIX进程只是多线程程序的一个特例,该进程只包含一个线程)
进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位
线程是轻量级的进程(LWP:Light Weight Process),在Linux环境下线程的本质仍是进程
查看指定进程的LWP号:ps -Lf pid
线程和进程的区别
进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换
调用fork()来创建进程的代价相对较高,即便利用写时复制技术,仍需要赋值诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork()调用在时间上的开销依然不菲
线程之间能够方便、快速地共享信息。只需将数据赋值到共享变量中即可
创建线程比创建进程通常要快10倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表
线程之间共享和非共享资源
线程操作
- pthread_t pthread_self(void);
- int pthread_equal(pthread_t t1,pthread_t t2);
- int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
- void pthread_exit (void *retval);
- int pthread_join(pthread_t thread,void **retval);
- int pthread_detach(pthread_t thread);
- int pthread_cancel(pthread_t thread);
在编译时最后加上-l 以及库名,如下,就能引用对应库
-lpthread
线程创建
一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程
程序中默认只有一个进程,fork()函数调用,2进程
程序中默认只有一个线程,pthread_create()函数调用,2个线程
#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,失败返回错误号,该错误号并非之前的errno
获取错误号的信息:char *strerrno(int errnum);
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void *callback(void * arg){
printf("child thread...\n");
printf("arg value : %d\n",*(int*)arg);
return NULL;
}
int main(){
pthread_t tid;
int num = 10;
//创建一个子线程
int ret = pthread_create(&tid,NULL,callback,(void*)&num);
if(ret != 0){
char * errstr = strerror(ret);
printf("error : %s\n",errstr);
}
for(int i = 0;i < 5;i++){
printf("%d\n",i);
}
sleep(1);
return 0;
}
线程退出
#include <pthread.h>
void pthread_exit(void *retval);
- 功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
- 参数:
- retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到
pthread_t pthread_self(void);
- 功能:获取当前的线程的线程ID
int pthread_equal(pthread_t t1,pthread_t t2);
- 功能:比较两个线程ID是否相等
不同的操作系统,pthread_t类型的实现不同,有的是无符号的长整型,有的是使用结构体去实现的
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void * callback(void* arg){
printf("child thread id: %ld\n",pthread_self());
return NULL;
}
int main(){
pthread_t tid;
//创建一个子线程
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
char* errstr = strerror (ret);
printf("error : %s\n",errstr);
}
//主线程
for(int i = 0;i < 5;i++){
printf("%d\n",i);
}
printf("tid : %ld,main thread id: %ld\n",tid,pthread_self());
//让主线程退出,当主线程退出时,不会影响其他正常运行的线程
pthread_exit(NULL);
//主线程结束后,接下来的代码不会执行
printf("main thread\n");
return 0;
}
线程参与
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
- 功能:和一个已经终止的线程进行连接
回收子线程的资源
这个函数是阻塞函数,调用一次只能回收一个子线程
一般在主线程中使用 - 参数:
- thread:需要回收的子线程的ID
- retval:接收子线程退出时的返回值
- 返回值:
0:成功
非0:失败,返回错误号
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
int value =10;
void * callback(void* arg){
printf("child thread id: %ld\n",pthread_self());
//sleep(2);
//return NULL;
//int value =10;
pthread_exit((void*)&value);//等同于return (void*)&value;
}
int main(){
pthread_t tid;
//创建一个子线程
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
char* errstr = strerror (ret);
printf("error : %s\n",errstr);
}
//主线程
for(int i = 0;i < 5;i++){
printf("%d\n",i);
}
printf("tid : %ld,main thread id: %ld\n",tid,pthread_self());
//主线程调用pthread_join()回收子线程的资源
int * thread_retval;
ret = pthread_join(tid,(void**)&thread_retval);
if(ret != 0){
char* errstr = strerror (ret);
printf("error : %s\n",errstr);
}
printf("exit data : %d\n",*thread_retval);
printf("回收子线程资源成功\n");
//让主线程退出,当主线程退出时,不会影响其他正常运行的线程
pthread_exit(NULL);
return 0;
}
线程分离
#include <pthread.h>
int pthread_detach(pthread_t thread);
- 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统
- 不能多次分离,会产生不可预料的行为
- 不能去连接一个已经分离的线程,会报错
- 参数:需要分离的线程的ID
- 返回值:
成功:0
失败:返回错误号
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void * callback(void *arg){
printf("child thread id:%ld\n",pthread_self());
return NULL;
}
int main(){
//创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
char * errstr = strerror(ret);
printf("error1 : %s\n",errstr);
}
//输出主线程和子线程的ID
printf("tid : %ld,main thread id : %ld\n",tid,pthread_self());
//设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放
ret = pthread_detach(tid);
if(ret != 0){
char * errstr = strerror(ret);
printf("error2 : %s\n",errstr);
}
//设置分离后,对分离的子线程进行连接pthread_join()
// ret = pthread_join(tid,NULL);
// if(ret != 0){
// char * errstr = strerror(ret);
// printf("error3 : %s\n",errstr);
// }
//会报错error3 : Invalid argument
pthread_exit(NULL);
return 0;
}
线程取消
#include <pthread.h>
int pthread_cancel(pthread_t thread);
- 功能:取消线程(让线程终止)
取消某个线程,可以终止某个线程的运行,但是并不是立马终止,而是当子线程执行到一个取消点,线程才会终止
取消点:系统规定好的一些系统调用,我们可以粗略的理解为从用户区到和内核区的切换,这个位置称之为取消点
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void * callback(void *arg){
printf("child thread id:%ld\n",pthread_self());
for(int i = 0;i < 5;i++){
printf("child %d\n",i);
}
return NULL;
}
int main(){
//创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid,NULL,callback,NULL);
if(ret != 0){
char * errstr = strerror(ret);
printf("error1 : %s\n",errstr);
}
//取消线程
ret = pthread_cancel(tid);
if(ret != 0){
char * errstr = strerror(ret);
printf("error2 : %s\n",errstr);
}
for(int i = 0;i < 5;i++){
printf("%d\n",i);
}
//输出主线程和子线程的ID
printf("tid : %ld,main thread id : %ld\n",tid,pthread_self());
pthread_exit(NULL);
return 0;
}
线程属性
int pthread_attr_init (pthread_attr_t *attr);
- 功能:初始化线程属性变量
int pthread_attr_destroy(pthread_attr_t *attr);
- 功能:释放线程属性的资源
int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate); - 功能:获取线程分离的状态属性
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate); - 功能:设置线程分离的状态属性
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void * callback(void *arg){
printf("child thread id:%ld\n",pthread_self());
return NULL;
}
int main(){
//创建一个线程属性变量
pthread_attr_t attr;
//初始化属性变量
pthread_attr_init(&attr);
//设置属性
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//获取线程的栈的大小
size_t size;
pthread_attr_getstacksize(&attr,&size);
printf("thread stack size : %ld\n",size);
//创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid,&attr,callback,NULL);
if(ret != 0){
char * errstr = strerror(ret);
printf("error1 : %s\n",errstr);
}
//输出主线程和子线程的ID
printf("tid : %ld,main thread id : %ld\n",tid,pthread_self());
//释放线程属性资源
pthread_attr_destroy(&attr);
pthread_exit(NULL);
return 0;
}