5守护进程与线程
进程组
多个进程的集合,第一个进程就是组长,组长进程的PID等于进程组ID。
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。与组长进程是否终止无关。
一个进程可以为自己或子进程设置进程组 ID
相关函数
pid_t getpgrp(void);
得到当前进程所在的进程组的组 IDpid_t getpgid(pid_t pid);
获取指定的进程所在的进程组的组 ID,参数 pid 就是指定的进程,0当前进程int setpgid(pid_t pid, pid_t pgid);
将某个进程移动到其他进程组中或者创建新的进程组参数:pid: 某个进程的进程 IDpgid: 某个进程组的组 ID如果 pgid 对应的进程组存在,pid 对应的进程会移动到这个组中,pid != pgid如果 pgid 对应的进程组不存在,会创建一个新的进程组,因此要求 pid == pgid, 当前进程就是组长了返回值:函数调用成功返回 0,失败返回 - 1
setpgid设置子进程组id
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main(int argc, char *argv[])
{pid_t pid;pid = fork();if(pid == 0 ){printf("我是子进程:%d,我的进程组id是:%d\n",getpid(),getpgrp());setpgid(pid,pid); //设置子进程的组id为自己的pid,默认是父进程的pidprintf("我是子进程:%d,我的进程组id是:%d\n",getpid(),getpgrp());}else if(pid > 0){sleep(1);printf("我是父进程:%d,我的进程组id是:%d\n",getpid(),getpgrp());wait(NULL);}return 0;
}
会话
多个进程组的集合
获取进程所属的会话 ID
pid_t getsid(pid_t pid);
成功:返回调用进程的会话 ID;失败:-1,设置 errno
pid 为 0 表示察看当前进程 session ID创建一个会话,并以自己的 ID 设置进程组 ID,同时也是新会话的 ID。
pid_t setsid(void);
成功:返回调用进程的会话 ID;失败:-1,设置 errno
调用了 setsid 函数的进程,既是新的会长,也是新的组长。
注意事项:
调用进程不能是进程组组长,该进程变成新会话首进程(session header),建立新会话时,先调用 fork, 父进程终止,子进程调用 setsid(),
#include <stdio.h>
#include <unistd.h>int main(int argc, char *argv[])
{pid_t pid;pid = fork();if(pid == 0){printf("我是子进程id:%d,进程组id:%d,会话id:%d\n",getpid(),getpgid(0),getsid(0));printf("change--------------\n");sleep(2);setsid();printf("我是子进程id:%d,进程组id:%d,会话id:%d\n",getpid(),getpgid(0),getsid(0));}else{sleep(3);printf("我是父进程id:%d,进程组id:%d,会话id:%d\n",getpid(),getpgid(0),getsid(0));}return 0;
}
编译执行结果
我是子进程id:44801,进程组id:44800,会话id:39560 #默认子进程的进程组id是父进程的pid,会话id是当前bash的pid
change--------------
我是子进程id:44801,进程组id:44801,会话id:44801 #setsid后,进程组id,会话id都变成子进程的pid
我是父进程id:44800,进程组id:44800,会话id:39560
守护进程
daemon进程。通常运行与操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作。
不受用户登录注销影响。通常采用以d结尾的命名方式。
Linux 后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。
创建守护进程
创建守护进程,最关键的一步是调用 setsid 函数创建一个新的 Session,并成为 Session Leader。
1. fork子进程,让父进程终止。2. 子进程调用 setsid() 创建新会话3. 通常根据需要,改变工作目录位置 chdir(), 防止目录被卸载。4. 通常根据需要,重设umask文件权限掩码,影响新文件的创建权限。 022 -- 755 0345 --- 432 r---wx-w- 4225. 通常根据需要,关闭/重定向 文件描述符6. 守护进程 业务逻辑。while()
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>void func(int signo)
{int fd1;fd1 = open("./lc.txt",O_RDWR|O_CREAT|O_APPEND,0664); //fd1 = 3 符合默认的习惯if(fd1 == -1 ){perror("open lc.txt error");exit(1);}char *str="heppy new year\n";write(fd1,str,strlen(str));close(fd1);
}int main(int argc, char *argv[])
{pid_t pid;int fd,fd1;pid = fork();if(pid > 0){exit(1);}pid_t pid1 = setsid();if(pid1 == -1 ){perror("open lc.txt error");exit(1);}chdir("/home/lc"); //切换到一个不可能被删除卸载的目录umask(0022); //默认umask是0002,默认创建普通文件默认属性是664close(STDIN_FILENO); //close 0fd = open("/dev/null",O_RDWR); //fd = 0if(fd == -1 ){perror("open /dev/null error");exit(1);}dup2(fd, STDOUT_FILENO); // 重定向 stdout和stderr 1 -->0dup2(fd, STDERR_FILENO); // 2--> 0,//捕捉信号struct sigaction act;act.sa_handler = func;act.sa_flags = 0;sigemptyset(&act.sa_mask);sigaction(SIGALRM,&act,NULL);
// signal(SIGALRM,func);//设置定时器struct itimerval it;it.it_interval.tv_sec = 10;it.it_interval.tv_usec = 0;it.it_value.tv_sec = 10;it.it_value.tv_usec = 0;setitimer(ITIMER_REAL,&it,NULL);while (1); // 模拟 守护进程业务.return 0;
}
线程
LWP,(light weight process)轻量级的进程,在linux环境下线程的本质仍然是进程,
进程:有独立的 进程地址空间。有独立的pcb。 分配资源的最小单位。可看成是只有一个线程的进程线程:有独立的pcb。没有独立的进程地址空间。 最小单位的执行。
[root@lc133 ~]# ps -aux | grep mysql | grep -v grep
mysql 1620 0.4 20.2 1793004 405880 ? Ssl 08:20 1:36 /usr/sbin/mysqld
Ssl:休眠,拥有子进程,多线程
[root@lc133 ~]# ps -Lf 1620
UID PID PPID LWP C NLWP STIME TTY STAT TIME CMD
mysql 1620 1 1620 0 38 08:20 ? Ssl 0:01 /usr/sbin/mysqld
mysql 1620 1 1669 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1670 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1671 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1672 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1674 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1675 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1676 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1677 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1678 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1679 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1680 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1724 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1725 0 38 08:20 ? Ssl 0:04 /usr/sbin/mysqld
mysql 1620 1 1726 0 38 08:20 ? Ssl 0:04 /usr/sbin/mysqld
mysql 1620 1 1727 0 38 08:20 ? Ssl 0:04 /usr/sbin/mysqld
mysql 1620 1 1728 0 38 08:20 ? Ssl 0:04 /usr/sbin/mysqld
mysql 1620 1 1729 0 38 08:20 ? Ssl 0:55 /usr/sbin/mysqld
mysql 1620 1 1786 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1787 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1788 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1793 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1794 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1797 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1799 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1802 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1803 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1804 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1811 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1812 0 38 08:20 ? Ssl 0:05 /usr/sbin/mysqld
mysql 1620 1 1814 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1815 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1816 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1817 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1830 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1831 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1832 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqld
mysql 1620 1 1836 0 38 08:20 ? Ssl 0:00 /usr/sbin/mysqldlwp:线程号
系统会给mysql进程1620分配进程地址空间,内核空间拥有pid1620的pcb。
pid1620进程创建线程时,本身也成了主线程。创建的线程分别拥有自己独立的pcb,处在同一内核空间中。
线程更加节省系统资源,效率不仅可以保持的,而且能够更高在一个地址空间中多个线程独享:每个线程都有属于自己的栈区,寄存器 (内核中管理的)1.线程 id2.处理器现场和栈指针(内核栈)3.独立的栈空间(用户空间栈)4.errno 变量5.信号屏蔽字6.调度优先级在一个地址空间中多个线程共享:代码段,堆区,全局数据区,打开的文件 (文件描述符表) 都是线程共享的1.文件描述符表2.每种信号的处理方式3.当前工作目录4.用户 ID 和组 ID5.内存地址空间 (.text/.data/.bss/heap/共享库)每个进程对应一个虚拟地址空间,一个进程只能抢一个 CPU 时间片一个地址空间中可以划分出多个线程,在有效的资源基础上,能够抢更多的 CPU 时间片一个进程创造线程并不是越多越好优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便
缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb 不支持 3. 对信号支持不好
优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、线程差别不是很大。
pthread_self获取当前线程id
获取线程 ID。其作用对应进程中 getpid() 函数。pthread_t pthread_self(void);返回值:成功:0; 失败:无!线程 ID:pthread_t 类型,本质:在 Linux 下为%lu无符号整数,其他系统中可能是结构体实现线程 ID 是进程内部,识别标志。(两个进程间,线程 ID 允许相同)注意:在子线程中通过使用pthread_self,获得线程id。
pthread_create创建线程
创建一个新线程。 其作用对应进程中 fork() 函数。int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);返回值:成功:0; 失败:错误号 -----Linux 环境下,所有线程特点,失败均直接返回错误号。参数:pthread_t:当前 Linux 中可理解为:typedef unsigned long int pthread_t;参数 1:传出参数,保存系统为我们分配好的线程 ID参数 2:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。参数 3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。参数 4:线程主函数执行期间所使用的参数。传参。
pthread_exit退出当前线程
void pthread_exit(void *retval); 退出当前线程。参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为 NULL几个退出函数:exit(); 退出当前进程。后续代码不会执行return: 返回到调用者那里去。pthread_exit(): 退出当前线程。不影响其他进程
循环创建线程
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>void *func(void *arg)
{int i = (int)arg;printf("我是第%d子线程线程id:%lu,主线程是:%d\n",i+1,pthread_self(),getpid());//return NULL;if(i==4){while(1);}pthread_exit(NULL);
}int main(int argc, char *argv[])
{pthread_t tid;int i;for(i = 0; i < 5; i++){int ret = pthread_create(&tid,NULL,func,(void*)i);if(ret != 0){perror("pthread_create error");exit(1);}}//sleep(1);printf("我是主线程线程id:%lu,主线程是:%d\n",pthread_self(),getpid());//while(1);//return 0;pthread_exit(NULL);
}
主线程 pthread_exit(NULL);只退出当先线程,不会对其他线程造成影响。
[lc@lc133 pthread]$ gcc pthread.c -lpthread
pthread.c: 在函数‘func’中:
pthread.c:8:10: 警告:将一个指针转换为大小不同的整数 [-Wpointer-to-int-cast]int i = (int)arg;^
pthread.c: 在函数‘main’中:
pthread.c:23:43: 警告:将一个整数转换为大小不同的指针 [-Wint-to-pointer-cast]int ret = pthread_create(&tid,NULL,func,(void*)i);^
[lc@lc133 pthread]$ ./a.out
我是主线程线程id:140307387344704,主线程是:25356
我是第3子线程线程id:140307362223872,主线程是:25356
我是第2子线程线程id:140307370616576,主线程是:25356
我是第1子线程线程id:140307379009280,主线程是:25356
我是第4子线程线程id:140307353831168,主线程是:25356
我是第5子线程线程id:140307345438464,主线程是:25356
[root@lc133 ~]# ps -aux | grep a.out #使用pthread_exit主线程已退出,子线程还在,使用return函数,主线程退出,子进程也会退出
lc 25356 100 0.0 0 0 pts/1 Zl+ 14:51 0:58 [a.out] <defunct>
root 25373 0.0 0.1 112840 2512 pts/0 S+ 14:52 0:00 grep --color=auto a.out
pthread_join阻塞 回收线程
int pthread_join(pthread_t thread, void **retval); 阻塞 回收线程。thread: 待回收的线程idretval:传出参数。 回收的那个线程的退出值。线程异常借助,值为 -1。返回值:成功:0失败:errno
pthread_exit可以传出指针,数值,要注意数据格式的强转。
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>struct student
{int age;char name[256];
};void *func(void *arg)
{printf("child thread,process id is %d,thread id is %lu\n",getpid(),pthread_self());struct student *luci;luci = malloc(sizeof(struct student));memset(luci,0,sizeof(luci));luci->age = 17;strcpy(luci->name,"luci");pthread_exit((void *)luci);
}int main(int argc, char *argv)
{printf("main thread,process id is %d,thread id is %lu\n",getpid(),pthread_self());pthread_t tid;int ret = pthread_create(&tid,NULL,func,NULL);if(ret != 0){perror("pthread_creade error");exit(1);}struct student *retval;//pthread_join 传出参数retval是void**类型的ret = pthread_join(tid,(void **)&retval);if(ret != 0){perror("pthread_jion error");exit(1);}printf("child thread exit.\n");printf("age = %d ,name = %s \n",retval->age,retval->name);return 0;
}
#include <stdio.h>
#include <pthread.h>void *func(void *arg)
{printf("child pthread id : %lu\n",pthread_self());pthread_exit((void*)66);
}int main(){pthread_t tid;int *retval;pthread_create(&tid,NULL,func,NULL);pthread_join(tid,(void**)&retval) ;printf("main pthread receive : %d\n",(void *)retval);pthread_exit(NULL);
}
pthread_detach 设置线程分离
int pthread_detach(pthread_t thread); 设置线程分离thread: 待分离的线程id返回值:成功:0失败:errno
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>void *func(void *arg)
{int i;for( i = 0;i < 5; i++){sleep(1);printf("i = %d \n",i);}pthread_exit(NULL);}
int main()
{pthread_t tid;int ret;ret = pthread_create(&tid,NULL,func,NULL);if(ret != 0){fprintf(stderr,"pthread_create error:%d",strerror(ret));exit(1);}pthread_detach(tid);ret = pthread_join(tid,NULL);//线程分离后主线程就不能回收了,报错:无效的参数if(ret != 0){fprintf(stderr,"pthread_join error:%s",strerror(ret));exit(1);}pthread_exit(NULL);return 0;
}
pthread_cancel杀死一个线程
int pthread_cancel(pthread_t thread); 杀死一个线程。 需要到达取消点(保存点)thread: 待杀死的线程id返回值:成功:0失败:errno在线程 A 中调用线程取消函数 pthread_cancel,指定杀死线程 B,这时候线程 B 是死不了的,只有在线程 B 中进程进行一次系统调用(从用户区切换到内核区),这个节点会被pthread_cancel杀死,否则线程 B 可以一直运行。与信号不同,进程会优先处理信号。如果,子线程没有到达取消点, 那么 pthread_cancel 无效。我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();成功被 pthread_cancel() 杀死的线程,返回 -1.使用pthead_join 回收。
pthread_equal判断线程id是否相同
int pthread_equal(pthread_t t1, pthread_t t2);参数:t1 和 t2 是要比较的线程的线程 ID返回值:如果两个线程 ID 相等返回非 0 值,如果不相等返回 0
与进程函数相比
线程控制原语 进程控制原语pthread_create() fork();pthread_self() getpid();pthread_exit() exit(); / return pthread_join() wait()/waitpid()pthread_cancel() kill()pthread_detach()
创建线程时设置线程分离属性
pthread_create函数中参数 pthread_attr_t 是一个结构体
typedef struct
{int detachstate; // 线程的分离状态int schedpolicy; // 线程调度策略structsched_param schedparam; // 线程的调度参数int inheritsched; // 线程的继承性int scope; // 线程的作用域size_t guardsize; // 线程栈末尾的警戒缓冲区大小int stackaddr_set; // 线程的栈设置void* stackaddr; // 线程栈的位置size_t stacksize; // 线程栈的大小
} pthread_attr_t;
步骤:pthread_attr_t attr 创建一个线程属性结构体变量pthread_attr_init(&attr); 初始化线程属性,成功:0;失败:错误号pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);设置线程属性为 分离态//detachstate: PTHREAD_CREATE_DETACHED(分离线程)// PTHREAD _CREATE_JOINABLE(非分离线程)//获取程属性,分离 or 非分离//int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate); //参数: attr:已初始化的线程属性pthread_create(&tid, &attr, tfn, NULL); 借助修改后的 设置线程属性 创建为分离态的新线程pthread_attr_destroy(&attr); 销毁线程属性,成功:0;失败:错误号
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>void *func(void *arg)
{printf("hello wrold.\n");pthread_exit(NULL);
}int main()
{pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);int val;pthread_attr_getdetachstate(&attr,&val);printf("属性:%d\n",val);pthread_t tid;pthread_create(&tid,&attr,func,NULL);pthread_attr_destroy(&attr);sleep(1);int ret = pthread_join(tid,NULL);if(ret != 0){fprintf(stderr,"pthread_join error : %s\n",strerror(ret));exit(1);}pthread_exit(NULL);return 0;
}
[lc@lc pthread]$ ./a.out
属性:1
hello wrold.
pthread_join error : Invalid argument
//pthread_join报错成功分离
注意事项
1. 主线程退出其他线程不退出,主线程应调用 pthread_exit
2. 避免僵尸线程
pthread_join
pthread_detach
pthread_create 指定分离属性
被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
3. malloc 和 mmap 申请的内存可以被其他线程释放
4. 应避免在多线程模型中调用 fork 除非,马上 exec,子进程中只有调用 fork 的线程存在,其他线程在子进程
中均 pthread_exit
5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制