进程组
多个进程的集合,第一个进程就是组长,组长进程的PID等于进程组ID。
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。与组长进程是否终止无关。
一个进程可以为自己或子进程设置进程组 ID
相关函数
pid_t getpgrp(void);
得到当前进程所在的进程组的组 ID
pid_t getpgid(pid_t pid);
获取指定的进程所在的进程组的组 ID,参数 pid 就是指定的进程,0当前进程
int setpgid(pid_t pid, pid_t pgid);
将某个进程移动到其他进程组中或者创建新的进程组
参数:
pid: 某个进程的进程 ID
pgid: 某个进程组的组 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,默认是父进程的pid
printf("我是子进程:%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- 422
5. 通常根据需要,关闭/重定向 文件描述符
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,默认创建普通文件默认属性是664
close(STDIN_FILENO); //close 0
fd = open("/dev/null",O_RDWR); //fd = 0
if(fd == -1 ){perror("open /dev/null error");exit(1);}
dup2(fd, STDOUT_FILENO); // 重定向 stdout和stderr 1 -->0
dup2(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/mysqld
lwp:线程号
系统会给mysql进程1620分配进程地址空间,内核空间拥有pid1620的pcb。
pid1620进程创建线程时,本身也成了主线程。创建的线程分别拥有自己独立的pcb,处在同一内核空间中。
线程更加节省系统资源,效率不仅可以保持的,而且能够更高
在一个地址空间中多个线程独享:每个线程都有属于自己的栈区,寄存器 (内核中管理的)
1.线程 id
2.处理器现场和栈指针(内核栈)
3.独立的栈空间(用户空间栈)
4.errno 变量
5.信号屏蔽字
6.调度优先级
在一个地址空间中多个线程共享:代码段,堆区,全局数据区,打开的文件 (文件描述符表) 都是线程共享的
1.文件描述符表
2.每种信号的处理方式
3.当前工作目录
4.用户 ID 和组 ID
5.内存地址空间 (.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: 待回收的线程id
retval:传出参数。 回收的那个线程的退出值。
线程异常借助,值为 -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. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制