- 仅用于防止自己遗忘
线程
int main(){
pthread_t th; //表示是一个新的线程
pthread_create(&th, NULL, funcion, NULL); // 四个参数 第一个参数 线程的地址,即上面创建的th,第二个参数忽略,一般为NULL,第三个参数是执行的函数的名字,第四个参数为所调用函数的参数
// 如果此时直接运行代码,而不管书写等待代码,那么 程序可能在创建了名为 th 的线程后,但是线程来不及运行 function 函数的内容 但是 主进程 就已经运行到return 0,直接结束了全部程序
pthread_join(th, NULL); // 在这个地方等待 th 线程结束
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
double nums[100];
int num = 0;
pthread_mutex_t lock;// 锁
void* funcion(void* args){ // 函数格式必须固定
for(int i=0; i<25; i++){
pthread_mutex_lock(&lock); // 上锁
nums[num++] = (rand() % 1000) * 1.0 /1000; // 这个地方直接 num++ 是不安全的,因为可能四个线程 依次 read到 num 到值,然后 再 依次 执行++,在将值 写回 num,则num的值只+1,而非+4,造成问题。这里用上锁解锁的方法解决问题
printf("num = %d th = %d\n", num, *(int *)args); // 重新将参数转为int类型 (*(int *)args)
pthread_mutex_unlock(&lock); // 解锁
}
return NULL;
}
typedef struct Node{
int star, fin, sum;
}Node;
void* function1(void* args){
Node *node = (Node *) args;
int star = node->star;
int fin = node->fin;
int sum = 0;
for(int i=star; i<fin; i++){
nums[i] = (rand() % 1000) * 1.0 /1000;
sum += nums[i];
}
node->sum = sum;
return NULL;
}
int main(){
pthread_t th1;
pthread_t th2;// 创建四个线程
pthread_t th3;
pthread_t th4;
pthread_mutex_init(&lock, NULL); // 初始化 锁
int a = 1, b = 2, c = 3, d = 4;
pthread_create(&th1, NULL, funcion, &a);// 线程传递参数
pthread_create(&th2, NULL, funcion, &b);// 如果一次想传入多个参数,则写个结构体就行了,结构体里面存着所有想传入的参数
pthread_create(&th3, NULL, funcion, &c);// 如果想返回一个或多个参数,则可以在结构体中定义多个变量,用于存放运算结果;因为pthread_create 不能返回结果,因此只能用这种 引用的方式 返回参数
pthread_create(&th4, NULL, funcion, &d);
pthread_join(th1, NULL);
pthread_join(th2, NULL);
pthread_join(th3, NULL);
pthread_join(th4, NULL);
for(int i=0; i<num; i++){
printf("%d = %lf\n", i, nums[i]);
}
return 0;
}
- 直接 num++ 是不安全的,因为可能四个线程 依次 read到 num 到值,然后 再 依次 执行++,在将值 写回 num,则num的值只+1,而非+4,造成问题
- 上述函数这样写 用时很长,因为每次都要上锁,解锁,如果num特别大,则时间都消耗在上锁解锁上了
typedef struct Node{
int star, fin, sum;
}Node;
void* function1(void* args){
Node *node = (Node *) args;
int star = node->star;
int fin = node->fin;
int sum = 0;
for(int i=star; i<fin; i++){
nums[i] = (rand() % 1000) * 1.0 /1000;
sum += nums[i];
}
node->sum = sum;
return NULL;
}
用结构体的方式来存放返回值,并表明for循环的上下界,能有效节省时间
exec族函数
// 让子进程去执行其他的程序
// exec函数族可以实现让子进程执行其他程序
// execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]……, 最后一个参数必须用空指针(NULL)作结束。
// 其中第一个参数file指向可执行文件名称,因为execlp()函数会从系统PATH路径中寻找相应命令,所以不需要带完整路径;
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
int main(){
pid_t pid;
pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
} else if(pid > 0){
sleep(1);
printf("parent process\n");
} else{
execlp("ls", "ls", "-l", NULL); // 最后一定加一个NULL
// 如果 参数列表变成 "ls" ,"-l" ,"-a",则执行的命令为 ls -a 而忽略掉了-l
}
return 0;
}
// execl 函数
// 加载一个进程,通过 路径+程序名 来加载
// 不能想execlp函数一样,execl没有path 环境变量加进来
// 通过这个函数,可以加载自定义函数
// execl("./a.out", "a", NULL);
僵尸进程/孤儿进程
-
孤儿进程
-
没有父进程回收的子进程称为孤儿进程
-
操作系统会把孤儿进程丢到孤儿院,由操作系统管理
-
在linux 操作系统中,孤儿进程会交给 pid 为1 的进程管理, /sbin/init
-
-
僵尸进程
- 子进程终止,父进程没有回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程
- 僵尸进程不能由kill杀死,因为僵尸进程已经死掉了
- 这里残留在pcb中的资源 是 有助于帮助父进程判断子进程的死法:运行结束,其他进程杀死……
ps aux 命令查看我们所运行的 a.out,能看到 一个 ./a 和 [a]
[a]代表a已经死掉了 也是死亡的意思
wait 函数
- 功能:
- 阻塞等待子进程退出(父进程等待子进程结束)
- 回收子进程残留资源
- 获取子进程结束状态(父进程获取子进程退出原因)
pid_t wait(int *status);
这个status 就是获取子进程的退出原因
如果wait返回值为-1则说明回收失败
-
WIFEXITED(status)为非0——>正常结束
WEXITSTATUS(status) 如上宏为 真,使用此宏–>获取进程退出状态(exit的参数) -
WIFSIGNALED(status) 为非0 ——>进程终止异常
WTERMSIG(status) 如上宏为真,使用此宏——>取得使进程终止的那个信号的编号 -
WIFSTOPPED(status) 为非0——>进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏——>取得使进程暂停的那个信号的编号
WIFCONTINUED(status)为真——>进程暂停后已经继续运行
一个wait()只回收一个子进程
waitpid(pid_t pid, int *status, int options)
- 指定pid来等待回收
- 成功:返回清理掉的子进程ID
- 失败:-1(无子进程)
- 第一个参数就是指定的pid,第二个是状态,第三个可以让父进程不阻塞,只是探探子进程死了没
- 参数pid:
>0
回收指定ID的子进程-1
回收任意子进程(相当于wait)0
回收和当前调用waitpid一个组的所有子进程<-1
回收指定进程组内的任意子进程
- 参数options
WNOHANG
非阻塞,回收子进程需要轮寻(定时查看子进程死没死)0
阻塞
- 参数pid:
IPC(Inter Process Communication)进程间通信
- 管道(最简单)
- 信号(开销最小)
- 共享营社区(无血缘关系)
- 本地套接字(最稳定,难度大,不好写)
pipe函数:匿名管道
管道的一般读写行为
fifo:有名管道
用于非血缘关系进程间通信
共享内存:mmap
函数参数使用注意事项(参数较多,容易出错)
管道
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递,调用pipe系统函数即可创建一个管道
- 其本质是一个伪文件(实为内核缓冲区)
- 由两个文件描述符引用,一个表示读端,一个表示写端
- 规定数据从管道的写端流入管道,从读端流出
管道的原理
:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现
管道的局限性
:
- 数据自己读不能自己写
- 数据一旦被读走,便不在管道中存在,不可反复读取
- 由于管道才用半双工通信方式,因此,数据只能一个方向流动
- 只能在有公共祖先的进程间使用管道
pipe函数
int pipe(int pipedf[2]);
成功:0
失败:-1
设置:errno
- pipe 函数打开一个用于读写的区域,并有两个端口:写端口,读端口
- 两个端口对应于传入的两个参数(int pipiedf[2])
由于子进程与父进程都公用同一个pipe,所以你需要手动设置谁读谁写
fd[0] -> 读
fd[1] -> 写
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
int main(){
int fd[2];
int ret = pipe(fd);
if(ret == -1){
perror("pipe create error");
}
pid_t pid = fork();
if(pid == -1){
perror("pid create error");
exit(1);
} else if(pid == 0){
// 子进程写数据,关闭读
close(fd[0]);
write(fd[1], "helloworld\n", strlen("helloworld\n"));
} else{
// 父进程写数据,关闭写
close(fd[1]);
char buf[1024];
ret = read(fd[0], buf, sizeof(buf));
if(ret == 0){
printf("-------\n");
}
write(STDOUT_FILENO, buf, ret);
}
return 0;
}
共享内存
mmap函数:创建共享内存(参数多,返回值与其他不一样)
借助共享内存存放磁盘文件(可以借助指针来访问磁盘文件)
父子进程之间,又血缘关系进程之间使用共享内存进行通信
- mmap 函数
void *mmap(void *adrr, size_t length, int prot, int flags, off_t offset);
- 返回值:成功:返回创建的映射区首地址;失败:MAP_FAILED宏
参数:
- addr:建立映射区的首地址,由Linux内核指定,使用时直接传递NULL
- length:想要创建映射区的大小
- prot:映射区权限 PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
- flags:标志为参数(常用于设定更新物理区域,设置共享,创建匿名映射区)
- MAP_SHARED:会将映射区所做的操作反映到物理设备(磁盘)上
- MAP_PRIVATE:映射区所做的修改不会反映到物理设备
- fd:涌来建立映射区的文件描述符
- offset:映射文件的偏移(4k整数倍)
非mmap写法
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(){
int fd1,fd2;
pid_t pid;
char buf[1024];
char *str = "---------test for shared fd in parent child process \n";
pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
}else if(pid == 0){
fd1 = open("test.txt", O_RDWR);
if(fd1 < 0){
perror("open error");
exit(1);
}
write(fd1, str, strlen(str));
}else {
fd2 = open("test.txt", O_RDWR);
if(fd2 < 0){
perror("open error");
exit(1);
}
sleep(1);// 保证子进程写入数据
int len = read(fd2, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
wait(NULL);
}
return 0;
}
- 使用mmap
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
struct STU{
int id;
char name[20];
char sex;
};
void sys_err(char *str){
perror(str);
exit(1);
}
int main(int argc, char *argv[]){
int fd;
struct STU studen = {10, "xiaoming", 'm'};
char *mm;
if( argc < 2){
printf("./aout file_shared\n");
exit(-1);
}
fd = open(argv[1], O_RDWR | O_CREAT, 0664);
mm = mmap(NULL, sizeof(studen), PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED)
sys_err("mmap");
close(fd);
while(1){
memcpy(mm, &studen, sizeof(studen));
studen.id++;
sleep(1);
}
munmap(mm, sizeof(studen));
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
typedef struct Node
{
double min, sum, max;
}Node;
Node get_MinAndSum(){
double sum = 0.0;
double min = 1.0;
double max = 0.0;
for(int i=0; i<25000; i++){
double t = (rand() % 1000 / 1000.0);
sum += t;
if(min > t)
min = t;
if(max < t)
max = t;
}
Node node;
node.min = min;
node.sum = sum;
node.max = max;
return node;
}
Node nodes[4];
int main(){
Node *p;
int fd;
fd = open("temp.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);//打开文件,没有就创建,有就置0,有读写权限
pid_t pid[4];
if(fd < 0){
perror("open error");
exit(1);
}
unlink("temp.txt");//删除临时文件目录项,使之具备被释放的条件,当所有占用该文件的进程结束之后,文件释放
ftruncate(fd, sizeof(nodes));// 创建文件的大小
p = (Node *) mmap(NULL, sizeof(nodes), PROT_READ |PROT_WRITE, MAP_SHARED, fd, 0);// 如果是MAP_SHARED,则父子内存共享内存
if(p == MAP_FAILED){
perror("create mmap error");
exit(1);
}
close(fd);//关闭文件
int i;
for(i=0; i<4; i++){
pid[i] = fork();
if(pid[i] == -1){
perror("create fork error");
exit(1);
}else if(pid[i] == 0){
Node node = get_MinAndSum();
p[i].min = node.min;
p[i].sum = node.sum;
p[i].max = node.max;
// printf("i = %d min = %lf, sum = %lf \n", i,node.min, node.sum);
exit(1);// 直接退出,防止子进程创建孙子进程
}
}
if(i == 4){// 如果i==4,则必定是父进程
waitpid(0, NULL, 0);// 等待进程执行完毕
double sum = 0.0, min = 1.1, max = 0.0;
for(i=0; i<4; i++){
// printf("min = %lf sum = %lf\n", p[i].min, p[i].sum);
if(p[i].min < min)
min = p[i].min;
if(p[i].max > max)
max = p[i].max;
sum += p[i].sum;
}
printf("总共100000数中 min = %lf sum = %lf max = %lf\n", min, sum, max);
int r = munmap(p, sizeof(nodes));
if(r == -1){
perror("free mmap error\n");
exit(1);
}
}
return 0;
}