初入C多线程

  • 仅用于防止自己遗忘

线程


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 函数

  • 功能:
  1. 阻塞等待子进程退出(父进程等待子进程结束)
  2. 回收子进程残留资源
  3. 获取子进程结束状态(父进程获取子进程退出原因)

pid_t wait(int *status);
这个status 就是获取子进程的退出原因
如果wait返回值为-1则说明回收失败

  1. WIFEXITED(status)为非0——>正常结束
    WEXITSTATUS(status) 如上宏为 真,使用此宏–>获取进程退出状态(exit的参数)

  2. WIFSIGNALED(status) 为非0 ——>进程终止异常
    WTERMSIG(status) 如上宏为真,使用此宏——>取得使进程终止的那个信号的编号

  3. WIFSTOPPED(status) 为非0——>进程处于暂停状态
    WSTOPSIG(status) 如上宏为真,使用此宏——>取得使进程暂停的那个信号的编号
    WIFCONTINUED(status)为真——>进程暂停后已经继续运行

一个wait()只回收一个子进程

waitpid(pid_t pid, int *status, int options)

  • 指定pid来等待回收
  • 成功:返回清理掉的子进程ID
  • 失败:-1(无子进程)
  • 第一个参数就是指定的pid,第二个是状态,第三个可以让父进程不阻塞,只是探探子进程死了没
    • 参数pid:
      1. >0回收指定ID的子进程
      2. -1回收任意子进程(相当于wait)
      3. 0 回收和当前调用waitpid一个组的所有子进程
      4. <-1 回收指定进程组内的任意子进程
    • 参数options
      1. WNOHANG 非阻塞,回收子进程需要轮寻(定时查看子进程死没死)
      2. 0 阻塞

IPC(Inter Process Communication)进程间通信

  1. 管道(最简单)
  2. 信号(开销最小)
  3. 共享营社区(无血缘关系)
  4. 本地套接字(最稳定,难度大,不好写)

pipe函数:匿名管道
管道的一般读写行为

fifo:有名管道
用于非血缘关系进程间通信

共享内存:mmap
函数参数使用注意事项(参数较多,容易出错)

管道

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递,调用pipe系统函数即可创建一个管道

  1. 其本质是一个伪文件(实为内核缓冲区)
  2. 由两个文件描述符引用,一个表示读端,一个表示写端
  3. 规定数据从管道的写端流入管道,从读端流出

管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现

管道的局限性

  1. 数据自己读不能自己写
  2. 数据一旦被读走,便不在管道中存在,不可反复读取
  3. 由于管道才用半双工通信方式,因此,数据只能一个方向流动
  4. 只能在有公共祖先的进程间使用管道
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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值