线程的同步机制、进程间的通信方式

1、线程的同步机制

线程同步,提前已经知道线程的执行顺序,让线程顺序执行的过程就是同步。

典型的就是生产者和消费者模型。

1.1、无名信号量API

1 >> 定义无名信号量:sem_t sem

2 >>初始化无名信号量:sem_init()

3 >> 获取信号量(P操作):sem_wait()    (获取不到就堵塞)

4 >> 释放信号量(V操作):sem_post()

5 >> 销毁信号量:sem_destory()

#include <head.h>

sem_t sem1, sem2;
void *task1(void *arg){
    printf("生产者线程\n");
    while(1){
        sleep(1);
        sem_wait(&sem2);
        printf("生产了一辆汽车\n");
        sem_post(&sem1);
    }
}
void *task2(void *arg){
    printf("消费者线程\n");
    while(1){
        sem_wait(&sem1);
        printf("购买了一辆汽车\n");
        sem_post(&sem2);
    }
}

int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;
    //中间的参数表示:0:两个线程同步,1:两个进程同步
    //最后一个参数:非0:表示线程可以获取信号量,0:表示线程获取不到信号量,将阻塞
    sem_init(&sem1, 0, 0);
    sem_init(&sem2, 0, 1);
    if(pthread_create(&tid1, NULL, task1, NULL)){
		perror("create thread 1 error");
		return -1;
	}
    if(pthread_create(&tid2, NULL, task2, NULL)){
		perror("create thread 2 error");
		return -1;
	}
    pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);

    sem_destroy(&sem1);
    sem_destroy(&sem2);

    return 0;
}

执行结果:

1.2、条件变量API

条件变量要与互斥锁一块使用

1 >> 定义条件变量:pthread_cond_t cond

2 >> 初始化条件变量:pthread_cond_init()

3 >> 获取条件变量:pthread_cond_wait()      

        使用流程:

                1.先获取互斥锁
                2.调用pthread_cond_wait
                   2.1将当前的线程放到队列中
                   2.2解锁
                   2.3在队列中休眠
                   2.4重新获取锁
                   2.5从队列中移除
                3.执行你的代码
                4.解锁

4 >> 释放条件变量:pthread_cond_signal()(释放一个资源)

                                        pthread_cond_broadcast()(释放所有的资源)

5 >> 销毁条件变量:pthread_cond_destroy()

#include <head.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

void *task1(void *arg){
    while(1){
        sleep(2);
        printf("生产了4辆汽车\n");
        pthread_cond_broadcast(&cond);
    }
}
void *task(void *arg){
    while(1){
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);
        printf("tid = %ld,购买了1辆汽车\n", pthread_self());
        pthread_mutex_unlock(&mutex);
    }
}

int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2, tid3, tid4, tid5;
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    if(pthread_create(&tid1, NULL, task1, NULL)){
        perror("create thread 1 error");
		return -1;
    }
    if(pthread_create(&tid2, NULL, task, NULL)){
        perror("create thread 2 error");
		return -1;
    }
    if(pthread_create(&tid3, NULL, task, NULL)){
        perror("create thread 3 error");
		return -1;
    }
    if(pthread_create(&tid4, NULL, task, NULL)){
        perror("create thread 4 error");
		return -1;
    }
    if(pthread_create(&tid5, NULL, task, NULL)){
        perror("create thread 5 error");
		return -1;
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    pthread_join(tid5, NULL);
    
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

执行结果:

 1.3、无名信号量与条件变量的使用场景

        无名信号量适合在线程数比较少的线程中实现同步过程,而条件变量适合在大量线程

实现同步过程。例如条件变量的使用场景如下:比如你要编写一个12306买票的服务器

当客户端访问服务器的时候,服务器会创建一个线程服务于这个用户。如果有多个用户

同时想买票,此时服务需要在瞬间创建一堆线程,这个时间比较长,对用户的体验感不好。

所以12306服务是在启动的时候都已经创建好一堆线程。调用pthread_cond_wait让这些

线程休眠,当有客户端请求买票的时候,只需要唤醒这些休眠的线程即可,有于省去了

创建线程的时候,所以这种方式的效率非常的高。

2、传统的进程间的通信方式

1、无名管道:只能用于亲缘间进程的通信,无名管道的大小是64K。

2、有名管道:可以用于亲缘间进程的通信,也可以用于非亲缘间的进程的通信。

3、信号:进程对信号的处理方式有三种:捕捉,忽略,默认

2.1、无名管道

#include <head.h>

int main(int argc, const char *argv[])
{
    //数组pipefd用于返回两个指向管道末端的文件描述符。
    //pipefd[0]指的是管道的读端。pipefd[1]指的是管道的写入端。
    int pipefd[2];
    pid_t pid;
    char buf[128] = {0};
    //创建管道
    if(pipe(pipefd)){
        perror("create pipe error");
		return -1;
    }
    //创建进程
    pid = fork();
    if(pid == -1){
        perror("fork error");
		return -1;
    }else if(pid == 0){
        close(pipefd[0]);   
        while(1){
            //从终端读取数据
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = '\0';
            //将数据通过管道写给父进程
            write(pipefd[1], buf, strlen(buf));
            if(strncmp(buf, "quit", 4) == 0){
                break;
            }
        }
        close(pipefd[1]);
        exit(-1);
    }else{
        close(pipefd[1]);
        while(1){
            //先清空 buf , 再从管道中读取数据
            memset(buf, 0, sizeof(buf));
            read(pipefd[0], buf, sizeof(buf));
            if(strncmp(buf, "quit", 4) == 0){
                break;
            }
            printf("read = %s\n", buf);
        }
        close(pipefd[0]);
        wait(NULL);
    }

    return 0;
}

执行结果:

 特点:    

1.只能用于亲缘间进程的通信

2.无名管道数据半双工的通信的方式

​         单工 : A -------------->B

        ​ 半双工 : 同一时刻 A----->B B------>A

​         全双工 : 同一时刻 A<---->B

3.无名管道的大小是64K

4.无名管道不能够使用 lseek 函数

5.读写的特点

        如果读端存在写管道:有多少写多少,直到写满为止(64k)写阻塞

        如果读端不存写管道,管道破裂(SIGPIPE) (可以通过gdb调试看现象)

        如果写端存在读管道:有多少读多少,没有数据的时候阻塞等待

        如果写端不存在读管道:有多少读多少,没有数据的时候立即返回

2.2、有名管道

main.c

#include <head.h>

int main(int argc, const char *argv[])
{
	if(mkfifo("./fifo", 0666)){
		perror("create fifo error");
		return -1;
	}
	//在这里堵塞,当不需要管道时,按回车清除管道
	getchar();

	system("rm ./fifo -f");

	return 0;
}

write.c

#include <head.h>

int main(int argc, const char *argv[])
{
    int fd;
    char buf[128] = {0};
    if((fd = open("./fifo", O_WRONLY)) == -1){
        perror("open error");
		return -1;
    }
    while(1){
        printf("input >>");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';
        write(fd, buf, strlen(buf));
        if(strncmp(buf, "quit", 4) == 0)
            break;
    }
    close(fd);

    return 0;
}

read.c

#include <head.h>

int main(int argc, const char *argv[])
{
    int fd;
    char buf[128] = {0};
    if((fd = open("./fifo", O_RDONLY)) == -1){
        perror("open error");
		return -1;
    }
    while(1){
        memset(buf, 0, sizeof(buf));
        read(fd, buf, sizeof(buf));
        if(strncmp(buf, "quit", 4) == 0)
            break;
        printf("read = %s\n", buf);
    }
    close(fd);

    return 0;
}

执行结果:

 特点:

1.可以用于任意进程间的通信

2.有名管道数据半双工的通信的方式

3.有名管道的大小是64K

4.有名管道不能够使用lseek函数

5.读写的特点

        如果读端存在写管道:有多少写多少,直到写满为止(64k)写阻塞

        如果读端不存写管道

​                 1.读端没有打开,写端在open的位置阻塞

​                 2.读端打开后关闭,管道破裂(SIGPIPE) (可以通过gdb调试看现象)

        如果写端存在读管道:有多少读多少,没有数据的时候阻塞等待

        如果写端不存在读管道

​                 1.写端没有打开,读端在open的位置阻塞

                ​ 2.写端打开后关闭,有多少读多少,没有数据的时候立即返回

2.3、信号

 常用的信号

1.在上述的信号中只有SIGKILL和SIGSTOP两个信号不能被捕捉也不能被忽略

2. 17)SIGCHLD,当子进程结束的时候,父进程收到这个SIGCHLD的信号

借助信号实现非阻塞方式回收子进程的资源

#include <head.h>

void signal_handle(int signo){
	printf("收到子进程退出的信号\n");
	//以非阻塞的方式回收
	waitpid(-1, NULL, WNOHANG);
	printf("子进程资源回收完成\n");
	//给父进程发信号,使其停止运行
	raise(SIGKILL);
}

int main(int argc, const char* argv[]) {
    pid_t pid;
    pid = fork();
    if (pid == -1) {
        perror("fork error");
        return -1;
    } else if (pid == 0) {
		printf("子进程执行中。。。\n");
        sleep(5);
        printf("子进程执行结束\n");
        exit(-1);
    } else {
        if (signal(SIGCHLD, signal_handle) == SIG_ERR) {
            perror("signal error");
            return -1;
        }
        while (1);
    }

    return 0;
}

执行结果:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值