zoukankan      html  css  js  c++  java
  • linux下的信号量PV操作进阶之路

    一.同步和互斥机制

    信号量

    互斥锁

    同步:指多个任务按照约定的先后次序相互配合来完成一件事情. 比如读线程等待写线程写完之后再去读.

    二.信号量-P/V操作

    P(s)含义:

    if(信号量>0)

      {

       申请资源的任务运行;

      信号量--;

      }

    else

    {申请资源的任务阻塞}

    V(S)含义:

    信号量++;

    if(有任务在等待资源)

    {

      唤醒等待的任务,让其继续运行.

    }

    三.Posix信号量:

    无名信号量(基于内存的信号量):多用于同一进程的多个线程之间.

    有名信号量:既可用于线程之间,也可用于进程之间.

    四.无名信号量常用函数:

    #include<semaphore>

    int sem_init(sem_t *sem, int pshared, unsigned int val);  初始化

    pshared: 0-线程间 1-进程间

    val:信号量初值.

    int sem_wait(sem_t *sem); P操作(可以这样理解,等下,我看看有没有资源)

    int sem_post(sem_t *sem); V操作

    五.结合例子讲解

    题目: 两个线程同步读写缓冲区(生产者/消费者问题,这里的读写是相对于缓冲区来说的)

     否定之否定的实现道路:

    1.错误实现1(自己一开始按照互斥思想的错误实现)

     在同一个线程中开始位置写上了sem_wait(&s),结束位置写上了sem_post(&s).这根本就不是信号量的用法,哈哈哈.

    2.错误实现2(把P操作放错位置)

    #include<stdio.h>
    #include<pthread.h>
    #include<semaphore.h>
    
    char buff[100];
    sem_t s;
    
    //用两个子线程来实现
    void *ReadTask(void *arg)
    {
        while(1)
        {
            if(strcmp(buff, "quit", 4) == 0)
            {
                printf("hehe, 遇到quit,退出
    ");
                break;
            } 
            else
            {
                sem_wait(&s);
                printf("所读内容:%s
    ", buff); //把数据从缓存区中读出
            }
        }
        pthread_exit("ReadTask End!");
    }
    
    void *WriteTask(void *arg)
    {
        do
        {
        scanf("%s", &buff); //把数据写到缓存区中
        sem_post(&s);
        }while(strcmp(buff, "quit", 4) != 0);
        pthread_exit("WriteTask End!");
    }
    
    int main()
    {
    if (sem_init(
    &s, 0, 0) < 0) //此处一开始将可用资源设为0.
    {
      perror("
    sem_init");
      exit(-1);  //直接退出线程
    }
    pthread_t t_read;
    pthread_t t_write;
    
    //即使这里先创建的t_read,也不代表t_read先执行.它们的执行顺序不固定的.
    int rc1 = pthread_create(&t_read, NULL, ReadTask, NULL);
    if(rc1<0)
    {
    perror("pthread_create t_read");
    exit(-1);
    }
    int rc2 = pthread_create(&t_write, NULL, WriteTask, NULL);
    if(rc2<0)
    {
    perror("pthread_create t_write");
    exit(-1);
    }
    pthread_join(t_read, NULL); pthread_join(t_write, NULL);
    return 0; }

    这里本来预想的结果是:当我输入"quit"后,直接执行if分支中的并退出线程.最后发现不是这个回事.发现还会进入到else的分支里,感觉很是费解.

    后来gdb调试,发现问题出现在了sem_wait这一行.

    当我敲入"quit"之前,读线程就已经执行到了sem_wait这里(因为buff符合else条件),只是因为信号量为0,所以阻塞在这里.等我敲入"quit"后,写线程将其写到buff中,然后唤醒了读线程,才有了下面的结果.

    quit
    所读内容:quit
    hehe, 遇到quit,退出

    3.对上面进行修改:

    只需把sem_wait放在线程循环一开始的地方.

    void *ReadTask(void *arg)
    {
            while(1)
            {
                    sem_wait(&s);
                    if(strcmp(buff, "quit", 4) == 0)
                    {
                            printf("hehe, 遇到quit,退出
    ");
                            break;
                    }
                    else
                    {
                            printf("所读内容:%s
    ", buff); //把数据从缓存区中读出
                    }
            }
            pthread_exit("ReadTask End!");
    }

    4.用主线程和子线程来实现:

    当输入"quit"时,主线程循环结束,主线程return 0,子线程也跟着结束了.

    为何要先初始化信号量,再创建线程呢?

    如果先创建线程的话,主线程和子线程谁先运行是不确定的,这带来什么影响呢?  如果把信号量初始化放在主线程中并且在子线程创建之后,如果子线程中内容先执行并且对信号量进行了操作,就会出现问题,因为此时信号量还没有初始化.

    #include<stdio.h>
    #include<pthread.h>
    #include<semaphore.h>
    
    char buff[100];
    sem_t s;
    
    //用一个子线程
    void *ReadTask(void *arg)
    {
        while(1)
        {
            sem_wait(&s);
            printf("所读内容:%s
    ", buff); //把数据从缓存区中读出
        }
        pthread_exit("ReadTask End!");
    }
    
    int main()
    {
    if(sem_init(&s, 0, 0))    //此处一开始将可用资源设为0.
    {
      perror("sem_init");
    exit(-1);
    }
    pthread_t t_read; //即使这里先创建的t_read,也不代表t_read先执行.它们的执行顺序不固定的. int rc1 = pthread_create(&t_read, NULL, ReadTask, NULL); if(rc1 < 0)
    {
      perror("pthread_create t_read");
    exit(-1);
    }
    do { scanf("%s", &buff); //把数据写到缓存区中 sem_post(&s); }while(strcmp(buff, "quit", 4) != 0); return 0; }

    5.以上几种情况使用一个信号量存在的问题,不是严格意义上的同步(严格意义上:写的时候,不要读;读的时候不要写):

    上面例子中的信号量实际上是针对读线程的,保证缓冲区中有数据时才可以去读.但是并没有针对写线程,因为需要保证在读的时候,不要往缓冲区中去写入.不然会造成读取异常的问题.

    举例:

    上述的例子中,如果读线程花的时间比较长,而写线程一直往里面写.就会导致前面的内容被后面的给覆盖掉,而读线程只是读到了后面的内容.

    void *ReadTask(void *arg)
    {
        while(1)
        {
            sem_wait(&s);
            sleep(5); //来模拟读的过程时间长
            printf("所读内容:%s
    ", buff); //把数据从缓存区中读出
        }
        pthread_exit("ReadTask End!");
    }

    可能的结果是

    aaa
    bbb
    ccc
    所读内容:ccc
    所读内容:ccc
    所读内容:ccc
    quit

    6.用两个信号量来实现同步过程:

    #include<stdio.h>
    #include<pthread.h>
    #include<semaphore.h>
    
    char buff[100];
    sem_t sem_r;
    sem_t sem_w;
    
    //用一个子线程
    void *ReadTask(void *arg)
    {
        while(1)
        {
            sem_wait(&sem_r); //判断sem_r是否大于0,是的话,就去--,申请资源的任务运行;否的话就阻塞
            sleep(2);
            printf("所读内容:%s
    ", buff); //把数据从缓存区中读出
            sem_post(&sem_w);//读的信号量++,如果有读操作处于阻塞,就把它给唤醒.
        }
        pthread_exit("ReadTask End!");
    }
    
    
    int main()
    {
        if(sem_init(&sem_w, 0, 1)<0)    //写信号量一开始为1
        {
            perror("sem_init sem_w");
            exit(-1);
        }
        if(sem_init(&sem_r, 0, 0)<0) //读信号量一开始为0
        {
            perror("sem_init sem_r");
            exit(-1);
        }
    pthread_t t_read;
    
    //即使这里先创建的t_read,也不代表t_read先执行.它们的执行顺序不固定的.
     if(pthread_create(&t_read, NULL, ReadTask, NULL)<0)
     {
        error("pthread_create t_read");
        exit(-1);
     }
    
    do
    {
        sem_wait(&sem_w);
        scanf("%s", &buff); //把数据写到缓存区中
        sem_post(&sem_r);
    }while(strcmp(buff, "quit", 4) != 0);
    return 0;
    }

    运行结果如下: 这个时候来不及处理的字符串会阻塞在sem_wait(&sem_w);它没有进到缓冲区中会在输入窗中排队.

    a
    b
    c
    d
    所读内容:a
    所读内容:b
    所读内容:c
    所读内容:d

    之前对PV操作只是感性的认识,今天结合这个教程学习了很多,谢谢这个老师了.

    视频链接:https://www.bilibili.com/video/BV1Fs411M7d5?p=2

    新战场:https://blog.csdn.net/Stephen___Qin
  • 相关阅读:
    cs硕士妹子找工作经历【阿里人搜等互联网】
    EJB到底是什么,真的那么神秘吗??
    到底EJB是什么
    安全性测试:
    掌握 Promise 的逻辑方法
    VS2019 不能下载的解决办法
    Java8基础系列-Stream
    周期模型(典型的几种):
    软件生存周期及其模型是什么?
    试述软件的概念和特点?软件复用的含义?构件包括哪些?
  • 原文地址:https://www.cnblogs.com/Stephen-Qin/p/12737678.html
Copyright © 2011-2022 走看看