进程组
也称之为作业。进程组ID等于第一个进程ID,因此可以通过进程ID是否等于进程组ID来判断该进程是不是进程组的组长。
只要进程组中有一个进程存在,进程组就存在,哪怕组长已经没了。
一个进程可以为自己或子进程设置进程组ID。
会话
由多个进程组组成。
创建会话要注意以下5点:
守护进程 daemon
守护进程常常以“d”结尾
创建守护进程模型
创建守护进程最关键的一步就是调用setsid函数创建一个新的Session,并成为Session Leader
创建一个守护进程:每份中在$HOME/log/ 创建一个文件 ,文件名—— 程序名.时间戳
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/time.h>
#include<time.h>
//通过宏定义文件名
#define _FILE_NAME_FORMAT_ "%s/log/mydaemon.%ld" //定义文件格式化
void touchfile(int num){
//获取家目录
char *HomeDir=getenv("HOME");
char strFilename[250]={0};
//获取时间戳的函数
//time(NULL);
sprintf(strFilename, _FILE_NAME_FORMAT_, HomeDir, time(NULL));
int fd=open(strFilename, O_RDWR|O_CREAT, 0666);
//文件打开失败
if(fd<0){
perror("open err");
exit(1);
}
close(fd);
}
int main(){
//创建守护进程的步骤如下
//创建子进程,父进程退出
pid_t pid=fork();
if(pid>0){
exit(1);
}
//当会长
setsid();
//设置掩码
umask(0);
//切换目录
//getenv获取环境变量
chdir(getenv("HOME"));//切换到家目录
//关闭文件描述符(一般通过测试之后,才会关闭文件描述符)
//close(0),close(1),close(2)
//执行核心逻辑
//设置一个定时器——每60秒来一次
struct itimerval myit={{60,0},{60,0}};
setitimer(ITIMER_REAL, &myit, NULL);
//注册捕获函数
struct sigaction act;
act.sa_flags=0;
//清空阻塞信号集
sigemptyset(&act.sa_mask);
//绑定捕获函数
act.sa_handler=touchfile;
//注册捕获函数
sigaction(SIGALRM, &act, NULL);
while(1){
//每隔一分钟在/home/itheima/log创建文件
sleep(1);
}
return 0;
}
守护进程扩展
指令nohup去执行进程就不会是收到第一个信号
通过nohup指令也可以达到守护进程的创建效果。(可以把进程输出重定向到特定的地方)
- nohup 指令会让cmd收不到SIGHUP信号
- & 代表后台运行
线程——是苦力,是真正干活的
同一个进程之间的线程,只有栈不是共享的。线程实际上就是一个函数,线程有自己的之行目的,任务十分明确。多线程可以更有效的利用CPU(前提是不止一个CPU)
- 线程——最小的执行单位
- 进程——最小的系统资源分配单位
线程和进程,在内核中都是通过clone函数实现的。从内核看,进程和线程没有区别,要看空间是否共享。
查看进程中线程的方法
ps -LF 进程号
线程共享的资源
线程不共享的资源
每个线程有自己的errno。
通过如下函数可以获得错误码对应的错误信息
char *strerror(int errnum);
线程的优缺点
创建一个线程
- thread 线程的ID,传出参数
- attr 代表线程的属性
- 第三个参数 函数指针, void *func(void*)
- arg 线程执行函数的参数
- 返回值 成功 0 失败 errno
编译时需要加 -lpthread库
注意:线程ID在进程内是唯一的,但是在整个操作系统内部不一定是唯一的。
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void *thr(void* arg){
//无符号长整型数 %lu
printf("I am a thread! pid=%d, tid=%lu\n", getpid(), pthread_self());
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
printf("I am main thread, pid=%d, tid=%lu\n", getpid(), pthread_self());
sleep(1);
return 0;
}
上面的代码存在一个问题:
就是如果主线程不睡眠,则另一个线程没有机会去执行(因为主线程打印之后就直接执行return 0了)。将上面代码中的sleep(1)换成如下语句即可:
pthread_exit(NULL);
线程退出函数
pthread_exit
注意事项:
- 线程中使用pthread_exit 来退出线程
- 线程中可以用 return(主控线程不行)
- exit代表退出整个进程
线程的回收
线程回收函数,阻塞等待
int pthread_join(pthread_t thread, void **retval);
- thread 创建的时候传出的第一个参数
- retval 代表的传出线程的退出信息(就是返回值)
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void *thr(void *arg){
printf("I am a thread, tid=%lu\n", pthread_self());
sleep(5);
printf("I am a thread, tid=%lu\n", pthread_self());
return (void*)100;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
void *ret;
//线程回收函数
pthread_join(tid, &ret);
printf("ret exit with %d\n",(int)ret);
pthread_exit(NULL);
return 0;
}
杀死线程
pthread_cancel
int pthread_cancel(pthread_t thread);
- 需要传入tid
- 返回值 失败——errno , 成功——0
测试代码
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void *thr(void*arg){
while(1){
printf("I am thread, very happy! tid=%lu\n", pthread_self());
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
sleep(5);
//杀死线程
pthread_cancel(tid);
void *ret;
//阻塞等待回收线程
pthread_join(tid, &ret);
printf("thread exit with %d\n", ret);
return 0;
}
如果把上面的代码中的thr函数中的printf动作和sleep动作注释之后,线程就杀不死了。因为pthread_cancle函数需要有一个取消点。
如果你的线程函数里面实在是没有取消点,你可以加上这样的一个函数
pthread_testcancel();
通过这个函数可以强行添加一个取消点。
线程分离
线程分离之后,这个线程就不需要你去回收了。
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void *thr(void *arg){
printf("I am a thread, self=%lu\n", pthread_self());
sleep(4);
printf("I am a thread, self=%lu\n", pthread_self());
return NULL;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
//线程分离
pthread_detach(tid);
sleep(5);
int ret=0;
//阻塞失败
if((ret=pthread_join(tid, NULL))>0){
printf("join err:%d, %s\n", ret, strerror(ret));
}
return 0;
}
执行了线程分离之后,pthread_join函数就回收失败了。
线程属性设置分离
创建那种易产生就直接是分离状的线程,不需要我们去执行detach函数来进程线程分离(因为执行detach函数还会有一些特殊情况,比如线程创建好之后很快就结束了,此时还没执行到detach函数)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void *thr(void *arg) {
printf("I am a thread \n");
return NULL;
}
int main() {
//设置线程属性
pthread_attr_t attr;
//初始化属性
pthread_attr_init(&attr);
//设置线程分离属性,这样线程创建好之后就直接分开了
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置属性分离
pthread_t tid;
//创建线程 第二个参数是线程属性
pthread_create(&tid,&attr,thr,NULL);
int ret;
//阻塞回收线程失败
if((ret = pthread_join(tid,NULL)) > 0){
printf("join err:%d,%s\n",ret,strerror(ret));
}
//摧毁属性
pthread_attr_destroy(&attr);
return 0;
}
比较两个线程IO是否相等
int pthread_equal(pthread_t t1, pthread_t t2);
创建多个子线程
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void *thr(void *arg){
int num=(int)arg;
printf("I am %d thread, self=%lu\n", num, pthread_self());
return (void*)(100+num);
}
int main(){
pthread_t tid[5];
for(int i=0; i<5; i++){
pthread_create(&tid[i], NULL, thr, (void*)i);
}
for(int i=0; i<5; i++){
void *ret;
pthread_join(tid[i], &ret);
printf("i =%d, ret=%d\n", i, (int)ret);
}
return 0;
}
线程注意事项
线程同步
多线程出现数据混乱的原因
mutex相关的函数
- restruct 约束该块内存区域队一行的数据,只能通过后面的变量进行访问和修改
- mutex 互斥量——锁
- attr 属性 可以不考虑,传NULL
给共享资源加锁
- mutex 就是 init初始化的那个锁
如果当前未锁,成功,加锁
如果当前已锁,阻塞等待
摧毁锁
- mutex 传入的锁
常量初始化
这么初始化之后,就不需要在用init了
Makefile模板
在/bin中,写一个makefile.template文件,文件内容如下:
#create by Rudy 20190108
SrcFiles=$(wildcard *.c)
TargetFiles=$(patsubst %.c,%,$(SrcFiles))
all:$(TargetFiles)
%:%.c
gcc -o $@ $<
clean:
-rm -f $(TargetFiles)
在 .bashrc 文件中加入这句
这样,执行 echomake 就可以自动生成makefile模板了
linux中的花哨技能
在 .bashrc 文件中加入这句
然后命令行就可以像vim中操作一样
就可以这么查找 gcc 文件。回车之后,就如下图了