Linux 学习记录26(进程线程篇)

Linux 学习记录26(进程线程篇)

在这里插入图片描述

一、多线程同步

所需头文件:#include <semaphore.h>

1. 概念

  1. 线程同步概念:已经知道线程的执行顺序,并且线程是顺序执行的
  2. 线程的同步机制适用于生产者消费者模型:在生产线上,例如线程1生产了一辆车,线程2购买这辆车,所以在线程执行时,必须线程1先执行,线程2再执行

2. 线程同步之无名信号量

无名信号量维护了一个value,生产者线程每生产一个,value就会自增1,消费者线程每消费一个,value就会自减1,当value为0时,生产者线程可以执行,但是消费者线程,阻塞等待。

(1. 无名信号量的API

定义信号量 :sem_t sem

>1初始化信号量
函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数1:信号量指针
参数2:线程号 
	0:多线程
	1:进程间
参数3:信号量格式,如果初始为1,则说明能够获取信号量,如果为0不能获取信号量
返回值:成功返回e,失败返回-1置位错误码
>2申请资源
函数原型:int sem_wait(sem_t *sem);
功能:申请资源(P操作 -1),如果申请不到资源,阻塞等待
参数1:信号量地址
返回值:成功返回0,失败返回-1,并置位错误码
>3释放资源
函数原型:int sem_post(sem_t *sem);
功能:释放资源(v操作 +1)
参数1:信号量指针
返回值:成功返回0,失败返回-1并置位错误码
>4销毁信号量
函数原型:int sem_destroy(sem t *sem);
功能:销毁信号量
参数1:信号量地址
返回值:成功返回e,失败返回-1置位错误码

(2. api的使用

sem_t sem1;//定义信号量
/*定义线程处理函数*/
void *task1(void *arg)
{
    int num = 10;
    while(num--)
    {
        sleep(2);//每两秒释放一次资源
        printf("子线程1释放了一个资源\r\n");
        sem_post(&sem1);//释放资源
    }
    printf("子进程1任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}
====================================================================
/*定义线程处理函数*/
void *task2(void *arg)
{
    int num = 10;
    while(num--)
    {
        sem_wait(&sem1);//申请资源
        printf("子线程2申请了一个资源\r\n");
    }
    printf("子进程2任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}
===================================================================
int main(int argc, char const *argv[])
{
    pthread_t tid1;//线程号
    pthread_t tid2;//线程号
    sem_init(&sem1,0,0);//初始化信号量

    if(pthread_create(&tid1, NULL,task1,NULL) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }
    if(pthread_create(&tid2, NULL,task2,NULL) != 0)
    {//如果创建失败
        perror("pthread task2");
        return -1;
    }
    
    printf("主线程 \r\n");
    printf("tid1: %#lx\ttid2: %#lx\r\n",tid1,tid2);
    pthread_join(tid1,NULL);//阻塞等待线程回收
    printf("线程1回收完成\r\n");
    pthread_join(tid2,NULL);
    printf("线程2回收完成\r\n");
    pthread_exit(EXIT_SUCCESS);
    return 0;
}

3. 线程同步之条件变量

在linux系统中,如果有多个线程需要同步的话,如果只使用无名信号量来完成,需要定义多个无名信号量,使用起来比较麻烦,此时我们可以采用系统开发的条件变量来完成这个工作
条件变量维护了一个队列,可以将多个线程先放入该队列中进行休眠,当有信号唤醒该线程时,那么该线程进入执行状态

(1. 条件变量的api

定义条件变量:pthread_cond t_cond = PTHREAD_COND_INITIALIZER:

>1动态初始化条件变量
函数原型:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
功能:动态初始化一个条件变量
参数1:条件变量地址
参数2:条件变量属性,一般为NULL
返回值:成功返回0,失败返回非0的错误码
>2唤醒一个休眠的进程
函数原型:int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒一个休眠的进程,该进程在条件变量中进行休眠等待的
参数1:条件变量的地址
返回值:成功返回0,失败返回非0的错误码
>3唤醒所有在等待休眠的线程
函数原型: int pthread_cond_broadcast(pthread_cond_t *cond);
功能:唤醒所有在等待休眠的线程
参数1:条件变量的地址
返回值:成功返回0,失败返回非0的错误码
>4阻塞等待条件变量
函数原型: int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:阻塞等待条件变量,在条件变量中维护了一个队列
参数1:条件变量地址
参数2:互斥锁,该锁的存在,就是为了解决在往队列中存放线程时,出现竞态用的
返回值:成功返回0,失败返回非0的错误码
>5销毁一个条件变量
函数原型: int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁一个条件变量
参数1:条件变量地址
返回值:成功返回0,失败返回非0的错误码

(2. 实现两个线程的同步

pthread_mutex_t mutex;//定义互斥锁
sem_t sem1;//定义信号量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//定义条件变量
/*定义线程处理函数*/
void *task1(void *arg)
{
    int num = 10;
    while(num--)
    {
        sleep(2);//每两秒释放一次资源
        printf("子线程1释放了一个资源\r\n");
        pthread_cond_signal(&cond);
    }

    printf("子进程1任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}
/*定义线程处理函数*/
void *task2(void *arg)
{
    int num = 10;
    while(num--)
    {
        pthread_cond_wait(&cond,&mutex);
        printf("子线程2申请了一个资源\r\n");
    }
    printf("子进程2任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}
int main(int argc, char const *argv[])
{
    pthread_t tid1;//线程号
    pthread_t tid2;//线程号
    pthread_cond_init(&cond,NULL);//动态初始化条件变量
    sem_init(&sem1,0,0);//初始化信号量
    if(pthread_create(&tid1, NULL,task1,NULL) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }
    if(pthread_create(&tid2, NULL,task2,NULL) != 0)
    {//如果创建失败
        perror("pthread task2");
        return -1;
    }
    
    printf("主线程 \r\n");
    printf("tid1: %#lx\ttid2: %#lx\r\n",tid1,tid2);
    pthread_join(tid1,NULL);//阻塞等待线程回收
    printf("线程1回收完成\r\n");
    pthread_join(tid2,NULL);
    printf("线程2回收完成\r\n");
    pthread_exit(EXIT_SUCCESS);
    return 0;
}

(3. 使用条件变量实现多线程同步问题

pthread_mutex_t mutex;//定义互斥锁
sem_t sem1;//定义信号量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//定义条件变量

/*定义线程处理函数*/
void *task1(void *arg)
{
    int num = 10;
    while(num--)
    {
        sleep(2);//每两秒释放一次资源
        pthread_mutex_lock(&mutex);//给临界区上锁
        printf("子线程1释放了一个资源\r\n");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);//给临界区解锁
    }

    printf("子进程1任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}
/*定义线程处理函数*/
void *task2(void *arg)
{
    int num = 10;
    while(num--)
    {
        pthread_mutex_lock(&mutex);//给临界区上锁
        pthread_cond_wait(&cond,&mutex);
        printf("子线程[ %#lx ]申请了一个资源\r\n",pthread_self());
        pthread_mutex_unlock(&mutex);//给临界区解锁锁
    }

    printf("子进程2任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}
int main(int argc, char const *argv[])
{
    pthread_t tid1;//线程号
    pthread_t tid2;//线程号
    pthread_t tid3;//线程号
    pthread_t tid4;//线程号
    pthread_t tid5;//线程号

    pthread_mutex_init(&mutex,NULL);//初始化互斥锁
    pthread_cond_init(&cond,NULL);//动态初始化条件变量
    sem_init(&sem1,0,0);//初始化信号量

    if(pthread_create(&tid1, NULL,task1,NULL) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }
    if(pthread_create(&tid2, NULL,task2,NULL) != 0)
    {//如果创建失败
        perror("pthread task2");
        return -1;
    }
    if(pthread_create(&tid3, NULL,task2,NULL) != 0)
    {//如果创建失败
        perror("pthread task3");
        return -1;
    }
    if(pthread_create(&tid4, NULL,task2,NULL) != 0)
    {//如果创建失败
        perror("pthread task4");
        return -1;
    }
    if(pthread_create(&tid5, NULL,task2,NULL) != 0)
    {//如果创建失败
        perror("pthread task5");
        return -1;
    }
    
    printf("主线程 \r\n");
    printf("tid1: %#lx\ttid2: %#lx\r\n",tid1,tid2);
    pthread_join(tid1,NULL);//阻塞等待线程回收
    printf("线程1回收完成\r\n");
    pthread_join(tid2,NULL);
    printf("线程2回收完成\r\n");
    pthread_mutex_destroy(&mutex);//销毁锁
    pthread_exit(EXIT_SUCCESS);
    return 0;
}

二、进程间的通讯(IPC)

  1. 对于线程间的通信,我们可以使用全局变量来实现,因为多个线程共享进程的资源
  2. 由于进程的内存空间是独立的,所以不能通过全局变量来进行进程间的通信
  3. 虽然可以使用尾部文件来实现多个进程间的通信,但是进程不能同步
  4. 由于每个进程0-3G是独立的内存,共享3-4G的内核空间,所以可以通过3-4G的内核空间来完成多个线程间的通信。
  5. 系统提供的通信方式一共有三种
  1. 传统内核提供的通讯机制

无名管道
有名管道
信号(signal)

  1. system V提供的进程间的通信

消息队列
共享内存
信号灯集

  1. 套接字(socket)

在这里插入图片描述

管道

  1. 管道原理:在进程的3-4G的内核空间在创建的一个特殊文件,管道可以传输不同进程间的数据,而且管道中的数据直接保存在内存
  2. 管道的特点:
  1. 管道是一个特殊文件,其他文件都是存储在磁盘上,而管道上的数据存储在内存上
  2. 管道遵循先进先出原则,而且管道文件不能使用lseek
  3. 由于管道文件在内核空间创建,所以但凡使用管道文件,只能使用文件IO进行操作,不能使用标准IO
  4. 管道的读取操作是一次性的,也就是管道中的数据被读取后就从管道中删除了
  5. 管道通信是一种半双工的通信方式

单工:只能单向发送数据
半双工:同一时间内只允许一个方向发送数据
全双工:同一时间内可以双向发送数据

  1. 对于管道而言,会提供读和写的两端,如果两端都被关闭了,管道就关闭了
  2. 管道文件的大小:64Kb=65535b
  3. 管道的读写问题

1. 无名管道

1. 无名管道:没有名字的管道文件
2. 由于没有文件名,所以不能所以open函数打开,使用对应函数pipe函数时就可以打开
3. 因为没有名字,所以其他进程不能使用该管道,使用该通信方式只适用于亲缘进程间的通讯
4. 无名管道文件在系统文件中是不可见的,使用两个无关的进程是无法拿到同一个处理该文件的描述符的

(1. 无名管道API

>1 创建一个无名管道文件
函数原型:int pipe(int pipefd[2]);
功能:创建一个无名管道文件
参数1:文件描述符数组,创建好文件后会提供读写两个文件描述符,放到数组中
	[0]:读端
	[1]:写端
返回值:成功返回0,失败返回-1,并置位错误码
================================================================
例:
int pipdfd[2];
pipe(pipefd);

2. 有名管道

1. 创建的管道文件是有名字的,能够在文件系统中进行找到别打开
2. 因为该管道文件存在,导致能够使用管道文件来完成亲缘和非亲缘间的进程通信

(1. 有名管道api

函数原型:int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道文件
参数1:管道的名字
参数2:创建管道的权限
返回值:成功返回0,失败返回-1,并置位错误码

思维导图

在这里插入图片描述

练习 使用有名管道完成两个无亲缘进程的沟通

1. 进程1

(主函数

int main(int argc, char const *argv[])
{
    /*创建管道文件*/
    if(mkfifo("./fifo1",0664))
    {//如果已创建就报错
        perror("mkfifo");
    }
    
    pthread_t tid1;//线程号
    pthread_t tid2;//线程号
    if(pthread_create(&tid1, NULL,write_fifo,NULL) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }

    if(pthread_create(&tid2, NULL,read_fifo,NULL) != 0)
    {//如果创建失败
        perror("pthread task2");
        return -1;
    }

    pthread_join(tid1,NULL);//阻塞等待线程回收
    printf("写fifio线程回收完成\r\n");
    pthread_join(tid2,NULL);
    printf("读fifio线程回收完成\r\n");

    return 0;
}

(写线程

/*写管道进程*/
void *write_fifo(void *argv)
{
    int fd = 0;
    char str[128] = {0};
    printf("正在打开管道文件fifo1...\r\n");
    if((fd=open("./fifo1",Rp)) == -1)
    {//只写打开管道文件1
        perror("op_fifo1");
        pthread_exit(NULL);//退出线程
    }
    printf("成功打开管道文件fifo1\r\n");

    while(1)
    {
        fgets(str,sizeof(str),stdin);//获取字符串
        str[strlen(str)-1] = '\0';
        if(strcmp(str,"over") == 0) 
        {
            write(fd,str,strlen(str));//写入管道
            break;
        }
        printf("向对方发送: %s\r\n",str);
        write(fd,str,strlen(str));//写入管道
    }
    close(fd);
    printf("写fifo任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}

(读线程

/*读管道进程*/
void *read_fifo(void *argv)
{
    int fd = 0;
    char str[128] = {0};

    printf("正在打开管道文件fifo2...\r\n");
    if((fd=open("../test2/fifo2",Rp)) == -1)
    {//只读打开管道文件2
        perror("op_fifo2");
        pthread_exit(NULL);//退出线程
    }
    printf("成功打开管道文件fifo2\r\n");

    while (1)
    {
        memset(str,0,sizeof(str));//数组清零
        read(fd,str,sizeof(str));

        printf("从对方接收: %s\r\n",str);
        if(strcmp(str,"over") == 0) 
        {
            printf("对方已关闭会话\r\n");
            break;
        }

    }
    close(fd);
    printf("读fifo任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}

(头文件

#ifndef __PUBLIC_H_
#define __PUBLIC_H_

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>
#include <pthread.h>
#include <semaphore.h>

#define R O_RDONLY
#define W O_WRONLY|O_CREAT|O_TRUNC
#define A O_WRONLY|O_CREAT|O_APPEND
#define Rp O_RDWR
#define Wp O_RDWR|O_CREAT|O_TRUNC
#define Ap O_RDWR|O_CREAT|O_APPEND

/*写管道进程*/
void *write_fifo(void *argv);
/*读管道进程*/
void *read_fifo(void *argv);


#endif

2. 进程2

(主函数

int main(int argc, char const *argv[])
{
    /*创建管道文件*/
    if(mkfifo("./fifo2",0664))
    {//如果已创建就报错
        perror("mkfifo");
    }
    
    pthread_t tid1;//线程号
    pthread_t tid2;//线程号
    if(pthread_create(&tid1, NULL,write_fifo,NULL) != 0)
    {//如果创建失败
        perror("pthread task1");
        return -1;
    }

    if(pthread_create(&tid2, NULL,read_fifo,NULL) != 0)
    {//如果创建失败
        perror("pthread task2");
        return -1;
    }

    pthread_join(tid1,NULL);//阻塞等待线程回收
    printf("写fifio线程回收完成\r\n");
    pthread_join(tid2,NULL);
    printf("读fifio线程回收完成\r\n");

    return 0;
}

(写线程

/*写管道进程*/
void *write_fifo(void *argv)
{
    int fd = 0;
    char str[128] = {0};
    printf("正在打开管道文件fifo2...\r\n");
    if((fd=open("./fifo2",Rp)) == -1)
    {//只写打开管道文件1
        perror("op_fifo2");
        pthread_exit(NULL);//退出线程
    }
    printf("成功打开管道文件fifo2\r\n");

    while(1)
    {
        fgets(str,sizeof(str),stdin);//获取字符串
        str[strlen(str)-1] = '\0';
        if(strcmp(str,"over") == 0) 
        {
            write(fd,str,strlen(str));//写入管道
            break;
        }
        printf("向对方发送: %s\r\n",str);
        write(fd,str,strlen(str));//写入管道
    }
    close(fd);
    printf("写fifo任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}

(读线程

/*读管道进程*/
void *read_fifo(void *argv)
{
    int fd = 0;
    char str[128] = {0};

    printf("正在打开管道文件fifo1...\r\n");
    if((fd=open("../test1/fifo1",Rp)) == -1)
    {//只读打开管道文件2
        perror("op_fifo1");
        pthread_exit(NULL);//退出线程
    }
    printf("成功打开管道文件fifo2\r\n");

    while (1)
    {
        memset(str,0,sizeof(str));//数组清零
        read(fd,str,sizeof(str));

        printf("从对方接收: %s\r\n",str);
        if(strcmp(str,"over") == 0) 
        {
            printf("对方已关闭会话\r\n");
            break;
        }

    }
    close(fd);
    printf("读fifo任务结束 关闭进程\r\n");
    pthread_exit(NULL);//退出线程
}

(头文件

#ifndef __PUBLIC_H_
#define __PUBLIC_H_

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>
#include <pthread.h>
#include <semaphore.h>

#define R O_RDONLY
#define W O_WRONLY|O_CREAT|O_TRUNC
#define A O_WRONLY|O_CREAT|O_APPEND
#define Rp O_RDWR
#define Wp O_RDWR|O_CREAT|O_TRUNC
#define Ap O_RDWR|O_CREAT|O_APPEND

/*写管道进程*/
void *write_fifo(void *argv);
/*读管道进程*/
void *read_fifo(void *argv);


#endif

完成效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值