• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • mutex互斥量(鎖)

    標簽: LInux  線程  多線程  信號  信號量  POSIX

    來看個 購票的 代碼

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <string>
    #include <stdlib.h>
    #include <string.h>
    
    int ticket = 100;
    void* sell(void * arg)
    {
      char** id = (char**) arg;
      while(1)
      {
        if(ticket > 0)
        {
          usleep(234212);//模擬購票過程 //這樣會導致 并發執行的出錯 ,即線程切換,出現的讀臟數據,解決策略是 加鎖
          printf("thread %s 購票成功,當前余量%d\n",*id,--ticket);
        }
        else 
        {
          break;
        }
      }
      return NULL;
    }
    
    int main ()
    {
      pthread_t  t1,t2,t3,t4;
      std::string a[4]={"thread 1","thread 2","thread 3","thread 4"};
      pthread_create(&t1,NULL,sell,&a[0]);
      pthread_create(&t2,NULL,sell,&a[1]);
      pthread_create(&t3,NULL,sell,&a[2]);
      pthread_create(&t4,NULL,sell,&a[3]);
    
      pthread_join (t1,NULL);
      pthread_join (t2,NULL);
      pthread_join (t3,NULL);
      pthread_join (t4,NULL);
    
    
      return 0;
    }
    

    在這里插入圖片描述
    在 sell函數中 執行到if 條件為真后,開始sleep,這時其他線程可能進入,產生讀臟數據。

    另外
    自增 自減操作本身并不是原子操作,
    而是對應三條匯編指令

    load 將共享變量 加載到 寄存器

    update 更新寄存器的值 執行 + 1 / -1 操作

    store 將新值從寄存器更新到共享變量的內存地址當中

    解決產生讀臟數據的思路就是 保證一個線程進入臨界區后,其他線程不能再進入了。

    方法:加鎖
    Linux 將這把鎖稱為互斥量

    來看 加了鎖的效果

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <string>
    #include <stdlib.h>
    #include <string.h>
    #include <sched.h>//cpu 控制
    int ticket = 100;
    pthread_mutex_t mutex;//define
    
    void* sell(void * arg)
    {
      char** id = (char**) arg;
      while(1)
      {
        pthread_mutex_lock(&mutex);//LOCK
        if(ticket > 0)
        {
          usleep(1000);//模擬購票過程 //這樣會導致 并發執行的出錯 ,即線程切換,出現的讀臟數據,解決策略是 加鎖
          printf("thread %s 購票成功,當前余量%d\n",*id,--ticket);
          pthread_mutex_unlock(&mutex);
          //sched_yield();//讓優先級大于本線程的線程執行
        }
        else 
        {
          pthread_mutex_unlock(&mutex);
          break;
        }
      }
      return NULL;
    }
    
    int main ()
    {
      pthread_t  t1,t2,t3,t4;
      pthread_mutex_init(&mutex,NULL);// init  lock 
    
      std::string a[4]={"thread 1","thread 2","thread 3","thread 4"};
      pthread_create(&t1,NULL,sell,&a[0]);
      pthread_create(&t2,NULL,sell,&a[1]);
      pthread_create(&t3,NULL,sell,&a[2]);
      pthread_create(&t4,NULL,sell,&a[3]);
    
      pthread_join (t1,NULL);
      pthread_join (t2,NULL);
      pthread_join (t3,NULL);
      pthread_join (t4,NULL);
    
      pthread_mutex_destroy(&mutex);// destroy lock 
    
      return 0;
    }
    

    在這里插入圖片描述

    線程安全:一段代碼被多個線程并發執行,不會出現不同的結果。(全局變量,靜態變狼比較常見)

    重入:重入是對函數而言,當一個執行流進入函數還沒出函數時,其他執行流也進入;
    這時若結果不會出現差錯,稱為可重入函數;否則,稱為不可重入函數。

    線程同步

    簡單例子:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <pthread.h>
    
    pthread_cond_t cond; //define  
    pthread_mutex_t mutex;
    
    void* r1(void* arg)
    {
      (void) arg;
      while(1)
      {
        pthread_cond_wait(&cond,&mutex);//等待條件滿足   再執行
        printf("活動\n");
      }
      return NULL;
    }
    
    void* r2(void* arg)
    {
      (void) arg;
      while(1)
      {
        pthread_cond_signal(&cond);//喚醒等待
        sleep(1);
      }
      return NULL;
    }
    
    
    int main()
    {
      pthread_t t1, t2;
    
      pthread_cond_init(&cond,NULL);//初始化 cond 條件變量
      pthread_mutex_init(&mutex,NULL);//初始化 lock
    
      pthread_create(&t1,NULL,r1,NULL);
      pthread_create(&t2,NULL,r2,NULL);
    
      pthread_join(t1,NULL);
      pthread_join(t2,NULL);
    
      pthread_mutex_destroy(&mutex);//釋放鎖
      pthread_cond_destroy(&cond);//釋放條件變量
    
      return 0;
    }
    

    同步: 在保證安全前提下,能讓線程按特定順序訪問臨界資源,從而避免饑餓問題。

    競態條件:因時許問題導致程序異常的情況。

    常用函數

    1 條件變量初始化函數
    pthread_cond_init(pthread_cond_t* cond,NULL);

    2 destroy:
    pthread_cond_destroy(pthread_cond_t* cond);

    3 等待條件滿足:
    pthread_cond_wait(pthread_cond_t* cond , pthread_mutex_t mutex );

    4 喚醒等待:
    pthread_cond_broadcast(pthread_cond_t* cond );

    pthread_cond_signal(pthread_cond_t* cond);

    5 不喚醒等待,pthread_cond_wait()函數將陷入等待

    但是我們看起來 互斥量 mutex好像無關緊要呀???,那么 為什么需要它?

    一: 條件不會無緣無故變得滿足,必然牽扯到共享數據的變化,所以需要有互斥量(鎖)來保證數據變化的原子性。,若沒有互斥鎖,將無法保證共享數據的修改和獲取安全

    二:條件等待是線程同步的一種手段,若只有一個線程,條件不滿足時,一直等待下去都不會滿足,這時就需要再來一個線程來改變共享變量,使條件滿足,并友好地通知該線程,現在條件滿足,可以執行了

    所以說 pthread_cond-wait 做了三件事
    1 釋放鎖,
    2 修改共享數據
    3 重新加鎖

    這樣在設計pthread_cond_wait 時,我們就得考慮內部的實現順序了 ,避免發生錯失信號,
    原則,先等待,再解鎖, 先上鎖 再修改,在解鎖,再發信號,喚醒等待。

    來看一個生產消費 ,阻塞隊列

    #include <iostream>
    #include <stdlib.h>
    #include <pthread.h>
    #include <queue>
    #include <unistd.h>
    
    #define NUM 50
    
    
    class BlockQueue
    {
    
      public:
    
        bool isFull()
        {
          return  q.size() == cap;
        }
    
        bool isEmpty()
        {
          return q.size() == 0;
        }
    
        void lockQueue()
        {
          pthread_mutex_lock(&lock);
        }
    
        void unLockQueue()
        {
          pthread_mutex_unlock(&lock);
        }
    
        void productWait()
        {
          pthread_cond_wait(&full,&lock);
        }
    
        void consumeWait()
        {
          pthread_cond_wait(&empty,&lock);
        }
    
        void NotifyConsume()
        {
          pthread_cond_signal(&full);
        }
    
        void NotifyProduct()
        {
          pthread_cond_broadcast(&empty);
        }
    
        BlockQueue(int cap_ = NUM)
          :cap(cap_)
        {
          pthread_mutex_init(&lock,NULL);
          pthread_cond_init(&full,NULL);
          pthread_cond_init(&empty,NULL);
        }
        void pushData(const int& data)
        {
          lockQueue();//加鎖
    
          while(isFull())//q  倉庫滿 陷入等待
          {
            NotifyConsume();//滿的話就通知消費者前來消費
            std::cout<<"queue isfull ,Notifyed consume data ,product stop,"<<std::endl;
            productWait();//生產陷入等待,等待消費 ,消費之后 isfull不成立,這時push_   
          }
          q.push(data);//q  倉庫有貨,通知消費者可以前來消費
          NotifyConsume();
    
          unLockQueue();//操作完成,解鎖
        }
    
        void popData(int& data)//輸出型參數
        {
          lockQueue();//首先加互斥鎖
    
          while(isEmpty())
          {
            NotifyProduct();//一旦沒貨,通知生產者生產
            std::cout<<"q is empty,notify product,consume stop,"<<std::endl;
            consumeWait();//消費陷入等待
          }
    
          data = q.front();
          q.pop();
          NotifyProduct();
    
          unLockQueue();
        }
    
    
        ~BlockQueue()
        {
          pthread_mutex_destroy(&lock);
          pthread_cond_destroy(&full);
          pthread_cond_destroy(&empty);
        }
    
    
      private:
    
        std::queue <int> q;//倉庫//阻塞隊列
        int cap ;//容量
        pthread_mutex_t lock;//互斥鎖
        pthread_cond_t  full;//庫滿
        pthread_cond_t  empty;//庫空 
    
    };
    
    
    void* consume(void* arg)
    {
      BlockQueue* bqp = (BlockQueue*)arg;
      int data;
      while(1)
      {
        bqp->popData(data);
        std:: cout<<"Consume  data:"<< data<<std::endl;
        sleep(2);
      }
      return NULL;
    }
    
    
    void* product(void* arg)
    {
      BlockQueue* bqp =(BlockQueue*)arg;
    
      srand((unsigned long)time(NULL));
      while(1)
      {
        int data =rand() % 50;
        bqp -> pushData(data);
        std::cout<<"product data "<<data<<std::endl;
        sleep(2);
      }
      return NULL;
    }
    
    int main()
    {
      BlockQueue bq;//阻塞隊列對象
      pthread_t t1,t2,t3,t4,t5,t6,t7;
    
      pthread_create(&t1,NULL,product,(void*)&bq);
      pthread_create(&t2,NULL,product,(void*)&bq);
      pthread_create(&t3,NULL,product,(void*)&bq);
      pthread_create(&t4,NULL,product,(void*)&bq);
      pthread_create(&t5,NULL,consume,(void*)&bq);
      pthread_create(&t6,NULL,consume,(void*)&bq);
      pthread_create(&t7,NULL,consume,(void*)&bq);
    
    
      pthread_join(t1,NULL);
      pthread_join(t2,NULL);
      pthread_join(t3,NULL);
      pthread_join(t4,NULL);
      pthread_join(t5,NULL);
      pthread_join(t6,NULL);
      pthread_join(t7,NULL);
    
      return 0;
    }
    

    POSIX信號量

    POSIX信號量 和System V信號量相同,都是用于同步操作,達到無沖突訪問臨界資源的目的,但POSIX信號量可以用于線程間同步,System V(共享內存,消息隊列,信號量)只能用于進程間

    頭文件 #include <semaphore.h>

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

    pshared: 0 表示線程共享,非0表示進程共享
    value 信號量初始值

    銷毀:
    int sem_destroy(sem_t* sem);

    等待信號量:
    int sem_wait(sem_t *sem); 將信號量值 -1

    發布信號量;資源使用完畢,可以歸還了
    int sem_post(sem_t *sem);信號量值 +1

    POSIX 信號量

    #include <iostream>
    #include <pthread.h>
    #include <stdlib.h>
    #include <semaphore.h>
    #include <vector>
    #include <unistd.h>
    
    #define NUM 16
    
    class RingQueue
    {
      private:
        std::vector<int>q;
        int cap;
        sem_t data_sem;
        sem_t space_sem;
        int consume_step;
        int product_step;
    
    
      public:
    
        RingQueue(int _cap = NUM)
          :q(_cap)//初始化 q的長度
           ,cap(_cap)
      {
        sem_init(&data_sem,0,0);//數據信號量,  0線程      0信號量初始值為 0
        sem_init(&space_sem,0,cap);//空間信號量  0: 線程 ,0:初值0
        consume_step=0;
        product_step=0;
      }
        void PutData(const int& data)
        {
          sem_wait(&space_sem);//申請資源     //等待信號量,等待被喚醒 ,執行后續
          q[consume_step] = data;
          consume_step++;
          consume_step %= cap;//循環操作
          sem_post(&data_sem); //v  釋放資源  //發布信號量
        }
    
        void GetData(int& data)
        {
          sem_wait(&data_sem); // P   //等待信號量
          data = q[product_step];
          product_step++;
          product_step %= cap;
          sem_post(&space_sem);
        }
    
        ~RingQueue()
        {
          sem_destroy(&data_sem);
          sem_destroy(&space_sem);
        }
    };
    
    void* consume(void* arg)
    {
      RingQueue *rqp = (RingQueue*)arg;
      int data ;
      while(1)
      {
        rqp->GetData(data);
        std::cout<<"consume data"<<data <<std::endl;
        sleep(1);
      }
      return NULL;
    }
    
    void* product(void* arg)
    {
      RingQueue *rqp =(RingQueue*) arg;
      srand((unsigned long) time(NULL));
      int  data;
      while(1)
      {
        data = rand() % 1024;
        rqp->PutData(data);
        std::cout<<"product data done: "<<data<<std::endl;
        sleep(1);
      }
      return NULL;
    }
    
    int main()
    {
      pthread_t t1 ,t2;
      RingQueue rq;
    
      pthread_create(&t1,NULL,product,(void*)&rq );
      pthread_create(&t2,NULL,consume,(void*)&rq );
    
      pthread_join(t1,NULL);
      pthread_join(t2,NULL);
    
      return 0;
    }
    
    版權聲明:本文為weixin_44030580原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/weixin_44030580/article/details/104107099

    智能推薦

    設計模式 Concurrency 之 Mutex 互斥鎖

    定義 例子 1. 定義 不同線程通過競爭進入臨界區(共享的數據和硬件資源),為了防止訪問沖突,在有限的時間內只允許其中之一獨占性的使用共享資源。 是Semaphore的特殊情況 當信號量的閾值設為1時就是互斥鎖模式 2. 例子 Lock Mutex Jar Thief App 測試結果...

    Linux利用mutex互斥鎖實現線程的互斥

    在多個線程對共享數據進行處理時,可能會發生沖突從而導致數據結果與預期結果不一樣,導致程序運行錯誤。 例:在一個主線程中有兩個線程,這兩個線程都對全局變量num進行+1操作。 注意:上述說的例子可能不會發生,只有當線程1還未將num的新值寫入內存時突然被切換到線程2才可能發生。            線程間的切換會在系統由內核態切換到用戶態...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    猜你喜歡

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    精品国产乱码久久久久久蜜桃不卡