線程的概念、控制、以及同步與互斥

      在學習了進程過進程之後,我們就該來學習一下線程了,什麼是線程呢?接下來我們就來學習一下。

1、線程的概念

         我們都知道,進程就是程序運行起來的實體,包括一大堆的數據結構,而線程是什麼呢?我們應該聽過一句話“線程是在進程內部運行的”,如何理解這句話呢?接下來帶着問題我們來學習。

(1)線程的概念

          什麼是線程?簡單來說,線程就是一個程序裏的執行流,更準確的說:線程是“一個進程內部的控制序列”。一個進程至少有一個執行線程(即主線程)

(2)線程和進程

  • 進程是資源競爭的基本單位;
  • 線程是程序執行的最小單位; 
  • 線程共享數據,但也有自己私有的一部分數據。          

                  >線程ID>一組寄存器>>errno>信號屏蔽字>調度優先級      

          在線程中,線程擁有自己的線程ID在內核中由LWP表示;線程私有的一組寄存器存有獨立的上下文數據;以及私有棧空間;當然也就有了私有的調度優先級;

  (3)一進程的多線程共享    

  •  同一地址空間; 既然地址空間都是共享的,那麼Text Segment、Data Segment都是共享的,如果定義一個函數,在各線程中都可以調用,如果定義一個全局變量,在各進程中都可以訪問到,除此之外,各線程還共享進程資源和環境;  
  • 文件描述符表
  • 每種信號的處理方式(忽略,默認動作,或者自定義)
  • 當前工作目錄
  • 用戶ID和組ID;           

(4)線程的優點

  • 創建一個新線程的代價比一個新進程小的多;
  • 與線程之間的切換相比,線程之間的切換需要操作系統做的工作要少的多;
  • 線程佔用的資源比進程少很多;
  • 能充分利用多處理器的可並行數量;
  • 在等待慢速I/O操作結束的同時,程序可執行其他的計算任務;
  • 計算密集型應用,爲了能在多處理器系統上運行,將計算分解到多個線程中實現;
  • I/O密集型應用,爲了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作;  

(5)線程的缺點

  • 性能損失;
  • 健壯性降低;一個線程崩掉,可能導致整個進程退出
  • 缺乏訪問控制;
  • 編程難度提高;

         在瞭解了線程之後,我們再來談談進程。我們在前邊學習了進程,在前邊,我們知道進程不簡單的是一段程序它是一段跑起來的程序,是一個實體,當我們學習了線程之後,進程就不能直接叫進程了。

           在Linux下,進程叫做輕量級進程;Linux下的一個進程由一個或多個PCB、以及資源組成;Linux下進程是承擔分配系統資源的實體;而在Linux下,調度的基本單位不再是進程而是線程;“線程是在進程內部運行的”這句話現在我們就能理解了,因爲進程和線程共享地址空間,所以線程在進程內部運行也就是線程在進程的地址空間中。在Linux下,進程控制塊和線程控制塊都由PCB表示,而在Windows操作系統下,線程控制塊叫做TCB。

2、可重入函數和線程安全   

   可重入和線程安全的概念?它們兩個有什麼區別和聯繫?

       一個函數被稱爲線程安全的,當且僅當被多個併發進程反覆調用時,它會一直產生正確的結果。反之,如果一個函數不是線程安全的,我們就說它是線程不安全的。線程不安全有四種情況:

  • 不保護共享變量的函數;
  • 函數狀態隨着調用改變的函數;
  • 返回指向靜態變量指針的函數;
  • 調用線程不安全函數的函數;

      可重入函數即表示可以被多個執行流重複進入

      可重入函數是線程安全的一種

線程安全和可重入的區別:

      1、可重入函數是線程安全函數的一種,其特點在於它們被多個線程調用時,不會引用任何共享數據。
可重入函數與線程安全的區別與聯繫:
  2、線程安全是在多個線程情況下引發的,而可重入函數可以在只有一個線程的情況下來說。
  3、線程安全不一定是可重入的,而可重入函數則一定是線程安全的。
  4、如果一個函數中有全局變量,那麼這個函數既不是線程安全也不是可重入的。
  5.如果將對臨界資源的訪問加上鎖,則這個函數是線程安全的,但如果這個重入函數若鎖還未釋放則會產生死鎖,因此是不可重入的。
  6、線程安全函數能夠使不同的線程訪問同一塊地址空間,而可重入函數要求不同的執行流對數據的操作互不影響使結果是相同的。

3、線程控制

       我們現在所學的線程都是在用戶級庫(POSIX線程庫)中使用,與線程有關的函數構成了一個完整的系列,絕大數函數的名字都是以“pthread_”打頭的,要使用這些數據庫,要通過引入頭文件<pthread.h>鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項。用戶級庫使用時需要加載到地址空間共享區。

(1)創建線程

          #include<pthread.h>

          int pthread_creat(pthread_t *thraed , const  pthread_attr_*attr ,

                                        void *(*start_routine)(void*) , void *arg);

         參數:thread  返回線程ID;

                   attr  設置線程屬性,默認爲NULL;

                   start_routine  爲回調函數地址,線程啓動後執行;

                   arg  傳給回調函數的參數;

         返回值:成功返回0,失敗返回錯誤碼; 

錯誤檢查:

  • 傳統的函數一般是成功返回0,失敗返回-1.並且對全局變量errno賦值以指示錯誤。
  • pthreads函數出錯時不會設置羣居變量errno(而大部分其他的POSIX函數會這樣做),而是將錯誤碼通過返回值返回。
  • pthreads同樣也提供了線程內的erron變量,以支持其他使用errno的代碼。對於pthreads函數的錯誤,建議通過返回值來確定,因爲讀取返回值要比讀取線程內的errno變量的開銷更小。

     下面用實例來展示一下:

creat.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>

void *Pthread_run(void *arg)
{
	int i=0;
	for(;;)
	{
		printf("I'm thread 1 \n");
		sleep(1);
	}
}

int main()
{
	pthread_t tid;
	int ret=pthread_create(&tid,NULL,Pthread_run,NULL);
	if(ret!=0)
	{
		fprintf(stderr,"pthread_creat :%s \n",strerror(ret));
		exit(EXIT_FAILURE);
	}
	int i=0;
	for(;;)
	{
		printf("I'm main thread\n");
		sleep(1);
	}
	return 0;
}

makefile

creat:creat.c
	gcc -o $@ $^ -lpthread

.PHONY:clean
clean:
	rm -f creat

(2)進程ID和進程ID

       在Linux中,目前的線程實現是Native Thread Libaray,簡稱NPTL。在這種實現下,線程又稱爲輕量級進程,每一個用戶態的線程,在內核中都對應一個調度實體,因爲擁有自己的進程描述符。

      沒有線程之前,一個進程對應內核裏的一個進程描述符,對應一個進程ID、但是在引入線程概念之後,情況發生了變化,一個用戶進程下管理N個用戶態線程,每個線程作爲一個獨立的調度實體在內核中擁有自己的進程描述符,進程和內核的描述符一下子變成了1:N的關係。

       Linux內核引入了線程組的概念。


      多線程的進程,又被稱爲線程組,線程組內的每一個線程在內核之中都存在一個進程描述符與之對應。進程描述符結構體中的pid,表面上看對應的是進程ID,其實不然,它對應的是線程ID;進程描述符中的tgid,含義是Thread  Group  ID ,該值對應的是用戶層面的進程ID。

用戶態系統調用內核進程描述符中對應的結構
線程IDpid_t  gettid(void)pid_t  pid
進程IDpid_t  getpid(void)pid_t  tgid

      現在說的進程ID,不同於pthread_t 類型的線程ID,和進程ID一樣,線程ID是pid_t類型的變量,而且是用來唯一標識線程的一個整形變量。

      查看一個線程的ID:


 (3)線程ID及進程地址空間佈局

  •  pthread_create函數會產生一個線程ID,存放在第一個參數指向的地址中。該線程ID和前面說的線程ID不是一回事。
  •  前面講的線程ID屬於進程調度的範疇。因爲線程是輕量級進程,是操作系統調度器的最小單位,所以需要一個數值來唯一標識該線程。
  •  pthread_create函數產生並標記在第一個參數指向的地址中的線程ID中,屬於NPTL線程庫的範疇。線程庫的後序操作,就是根據該線程ID來操作線程的。
  •  線程庫NPTL提供了pthread_self函數,可以獲得自身的ID。

         pthread_t   pthread_self(void);

   pthread_t的類型取決於實現,對於Linux目前實現的NPTL實現而言,pthread_t類型的線程ID,本質就是一個進程地址空間上的一個地址。


(4)線程終止

        我們知道,進程退出有三種情況,那麼線程呢?線程退出和進程退出有一點不同,就是線程退出不存在代碼沒跑完異常退出的情況,也就是說,線程退出有兩種情況,一是代碼跑完結果正確,二是代碼跑完結果不正確。

       線程退出的三種方式:(值終止線程,而不終止進程)

  • 從線程函數return(void *),這種方法對主線程不適用,從main函數return相當於調用exit。
  • 線程可以調用pthread_exit(void *)終止自己,線程不能調用exit。
  • 線程是可以被取消的,一個線程可以調用函數pthread_cancel  終止同一進程中的另一個線程。    

     下面說一說線程退出的兩個函數:

        void pthread_exit(void  *value_ptr);       線程終止函數

         參數:value_ptr 不要指向一個局部變量;

         無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身);

      注意:pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是malloc分配的,不能在線程函數的棧上分配,因爲當其他線程得到這個返回指針時線程函數已經退出了。

         int  pthread_cancel(pthread_t  thread);取消一個執行中的線程

         參數:thread  線程ID

         返回值: 成功返回0,失敗返回錯誤碼;

                        線程被取消,返回-1             

4、線程等待與分離

(1)線程等待

       爲什麼需要線程等待?

       已經退出的線程所佔有的空間未被釋放,任然存在於進程的地址空間內,造成內存泄漏;創建新的線程時是不會複用剛纔退出的線程的地址空間;如果不等待,主線程直接退出,就標誌着進程退出,線程根本沒有機會來執行代碼,就會被釋放。

     線程等待函數:

     int  pthread_join(pthread_t  thread,  void  **value_ptr );

     參數:thread  線程ID

               value_ptr指向一個指針,而該指針指向線程的返回值;

     返回值:成功返回0,失敗返回錯誤碼;

     調用該函數的線程將掛起等待,知道ID爲thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的進程終止狀態是不同的:

  • 如果thread線程通過return返回,value_ptr所指向的單元裏存放的是thread線程函數的返回值。
  • 如果thread線程被別的線程調用pthread_cancel異常終止,value_ptr所指向的單元裏存放的是常熟PTHREAD_CABCELED(定義的宏,爲(void *)-1)。
  • 如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ptr參數。

(2)分離線程

       默認情況下,一個新創建的線程是可結合的(joinable),意思就是在線程結束後,需要調用pthread_join函數進行等待回收,否則無法回收釋放資源,從而造成內存泄漏。

      如果不關心線程的返回值,join是一種負擔,這個時候,我們可以告訴系統,當線程退出後,自動釋放線程資源。

      int pthread_detachn(pthread_t  thread);

      分離線程可以是自己提出的,也可以由主線程對該線程進行分離

      pthread_detach(pthread_self);

     一個線程被分離,就不需要被pthread_join,joinable和分離是衝突的,一個線程不能既是joinable又是分離的。

   舉栗子說明自己分離:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>

void *thread_run(void *arg)
{
	pthread_detach(pthread_self());
	printf("%s \n",(char *)arg);
	return NULL;
}

int main()
{
	pthread_t tid;
	int id=pthread_create(&tid,NULL,thread_run,"thread 1 is running\n");

	if(id!=0)
	{
		printf("creat thread failure\n");
		return 1;
	}
	int ret=0;
	sleep(1);

	if(pthread_join(tid,NULL)==0)
	{
		printf("waiting succeed\n");
		ret=0;
	}
	else
	{
		printf("waiting failed\n");
		ret=1;
	}
	return ret;
}

  結果是先出現一行“thread 1 is  running”,然後一秒後,等待失敗。原因就是在那一秒中,thread 1已經把自己給分離了。

   一個線程出錯掛掉,則相對應的進程也會掛掉;一個被分離的線程異常掛掉,進程依舊也會退出。

5、線程同步與互斥

       在學習進程間通信時,我們知道要想讓進程間進行通信,就必須讓兩個進程同時看到同一份公共資源,也就是臨界資源,然後纔有可能進行進程間通信,但是,當兩個進程進行通信時會產生一些小問題,所以我們就說到了同步與互斥,當然兩個線程間進行通信時,不必那麼麻煩,因爲它們兩個本來就在同一個進程中,大多數資源都是共享的,但是只要有共享的地方,肯定就會出現這樣那樣的問題,所以,線程間也需要同步與互斥機制。

 (1)mutex互斥量

        大部分情況下,線程使用的數據都是局部變量,變量的地址空間在線程自己私有棧空間內,這種情況,變量歸屬於單個線程,其他線程無法獲得這些變量。但有時候,很多變量需要在線程間共享,這樣的變量稱爲共享變量,可以通過數據的共享,完成線程之間的交互。但是,多個線程併發的操作共享變量,會會帶來一些問題。

      例如下面操作共享變量而出現問題的售票系統:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int ticket=100;

void *sellticket(void *arg)
{
   char *tid=(char *)arg;
   while(1)
   {
      if(ticket>0)
      {
         usleep(1000);
	 printf("%s sells ticket:%d \n",tid,ticket);
	 ticket--;
      }
      else
      { 
         break;
      }
   }
}


int main()
{
   pthread_t t1,t2,t3,t4;

   pthread_create(&t1,NULL,sellticket,"thread 1");
   pthread_create(&t2,NULL,sellticket,"thread 2");
   pthread_create(&t3,NULL,sellticket,"thread 3");
   pthread_create(&t4,NULL,sellticket,"thread 4");

   pthread_join(t1,NULL);
   pthread_join(t2,NULL);
   pthread_join(t3,NULL);
   pthread_join(t4,NULL);
}

      你會發現結果有些不對勁,怎麼還會有負數的票,用實際來說就是一張票賣給了不同的兩個人,這個怎麼可能?根本不符合現實,爲什麼會這樣呢?

      if語句判斷條件爲真以後,代碼可以併發的切換到其他線程;在usleep 的過程中,可能會有很多個線程會進入該代碼段;而且ticket--這個操作也不是一個原子操作,ticket--這一句C語言代碼對應的彙編代碼有三條,先將ticket變量由內存加載到寄存器中,然後更新寄存器裏面的值(即進行-1操作),再將減完後的值寫回到內存中。

         要解決以上的問題,就要做到:

  • 代碼必須要有互斥行爲:當代碼進入臨界區執行時,不允許其他線程進入該臨界區;
  • 如果多個線程同時要求執行臨界區的代碼,並且臨界區沒有線程執行,那麼只能運行一個線程進入該臨界區;
  • 如果線程不在臨界區中執行,那麼該線程不能阻止其他線程進入臨界區;

要做到這三點,本質上就需要一把鎖,而在Linux中提供的這把鎖叫互斥量。

而鎖要在進入臨界區之前加,在出了臨界區之後就解鎖。

(2)互斥量的接口

     初始化互斥量:

           靜態分配:定義一個宏,直接賦值

                pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;

           動態分配:使用庫函數進行初始化

               int  pthread_mutex_init(pthread_mutex_t  *restrict mutex , const                                              pthread_mutexattr_t  *restrict  attr);

               參數:mutex 需要初始化的互斥量

                         attr  默認爲NULL

    銷燬互斥量:

         銷燬互斥量需要注意:靜態分配的互斥量不需要銷燬;不要銷燬一個已經加鎖的互斥量;已經銷燬的互斥量,要確保後面不會有線程再嘗試加鎖。

           int  pthread_mutex_destroy(pthread_mutex_t  * mutex );

     互斥量加鎖和解鎖:

           int  pthread_mutex_lock(pthread_mutex_t  * mutex );

           int  pthread_mutex_unlock(pthread_mutex_t  * mutex );

       返回值:成功返回0,失敗返回錯誤碼;

調用pthread_lock時,可能會遇到以下情況:

互斥量處於未鎖狀態,該函數會將互斥量鎖定,同時返回成功;

發起函數調用時,其他線程已經鎖定互斥量,或者存在其他線程同時申請互斥量,但沒競爭到互斥量,那麼pthread_lock調用會陷入阻塞,等待互斥量解鎖。  

    給上面的例子加個互斥量改進一下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sched.h>

int ticket=100;
pthread_mutex_t mutex;

void *sellticket(void *arg)
{
   char *tid=(char *)arg;
   while(1)
   {  
      pthread_mutex_lock(&mutex);
      if(ticket>0)
      {
         usleep(1000);
	 printf("%s sells ticket:%d \n",tid,ticket);
	 ticket--;
	 pthread_mutex_unlock(&mutex);
      }
      else
      { 
         pthread_mutex_unlock(&mutex);
         break;
      }
   }
}


int main()
{
   pthread_t t1,t2,t3,t4;

   pthread_mutex_init(&mutex,NULL);

   pthread_create(&t1,NULL,sellticket,"thread 1");
   pthread_create(&t2,NULL,sellticket,"thread 2");
   pthread_create(&t3,NULL,sellticket,"thread 3");
   pthread_create(&t4,NULL,sellticket,"thread 4");

   pthread_join(t1,NULL);
   pthread_join(t2,NULL);
   pthread_join(t3,NULL);
   pthread_join(t4,NULL);

   pthread_mutex_destroy(&mutex);
}

(3)條件變量

      當一個線程互斥地訪問某個變量時,它可能發現在其他線程改變狀態之前,它什麼也做不了。例如當一個線程線程訪問隊列時,發現隊列爲空,它只能等待,只到其他線程將一個節點添加到隊列中,而這種時候就需要用到條件變量。這個條件變量將相當於給這個線程發送的信號一樣,當該線程接收到信號時,就知道隊列不爲空了,然後就可以訪問了。

   條件變量函數:

       int  pthread_cond_init(pthread_cond_t  *restrict cond, const pthread_condattr_t                                               *restrict  attr);     初始化

        參數:cond  要初始化的變量 

                   attr  默認爲NULL;

         int pthread_cond_destroy(pthread_cond_t  *cond);  銷燬

         int  pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t  

                                                *restrict  mutex);   等待條件滿足   等待時釋放鎖

         

         int  pthread_cond_broadcast(pthread_cond_t  *cond );廣播給全部線程通知

         int  pthread_cond_signal(pthread_cond_t  *cond );給某個線程單獨通知

  簡單案例

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;

void *r1(void *arg)
{
   while(1)
   {  
      pthread_cond_wait(&cond,&mutex);
	  printf("active \n");
   }
}

void *r2(void *arg)
{
   while(1)
   {  
      pthread_cond_signal(&cond);
	  sleep(1);
   }
}

int main()
{
   pthread_t t1,t2;

   pthread_cond_init(&cond,NULL);
   pthread_mutex_init(&mutex,NULL);

   pthread_create(&t1,NULL,r2,"thread 1");
   pthread_create(&t2,NULL,r1,"thread 2");

   pthread_join(t1,NULL);
   pthread_join(t2,NULL);
  
   pthread_mutex_destroy(&mutex);
   pthread_cond_destroy(&cond);
}

 結果就是每隔一秒打印一個active。

爲什麼pthread_cond_wait需要互斥量?

  • 等待條件是線程間同步的一種手段,如果只有一個線程,條件不滿足,一直等下去都不會滿足,所有必須要有一個線程通過某些操作,改變共享變量,使原先不滿足的條件變得滿足,並且友好的通知等待在條件變量上的線程。
  • 條件不會無緣無故的變得滿足,必然會牽扯到共享數據的變化。所有一定要用互斥鎖來保護,沒有互斥鎖就無法安全的獲取和修改數據。 

  其實簡單來說就是用來釋放鎖,監測條件變量。但必須是原子的。

(4)條件變量使用規範

       等待條件代碼:

pthread_mutex_lock(&mutex);
while(條件爲假)
	pthread_cond_wait(cond,mutex);
修改條件
pthread_mutex_unlock(&mutex);

          給條件發送信號代碼:

pthread_mutex_lock(&mutex);
 設置條件爲真
pthread_cond_signal(cond);
pthread_mutex_inlock(&mutex);

(5)生產者消費者模型

  生產者消費者模型簡單來說就是“321”原則,“321”原則就是三種關係(生產者與生產者之間互斥關係,消費者與消費者之間互斥關係,消費者與生產者之間互斥且同步),兩種角色(生產者與消費者),一個交易場所(可以是各種數據結構,鏈表,數組)。

  最簡單的就是一個生產者一個消費者,然後再加一個交易場所。(兩個線程來模擬生產者消費者模型)---基於鏈表

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>
#include<assert.h>

typedef struct  linklistNode
{
	int key;
	struct linklistNode *Next;
}Node,*pNode,**ppNode;

pNode head;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
void Initlinklist(ppNode head)
{
	assert(head);
    (*head)->Next=NULL;
}
int isEmpty(pNode head)
{
	return head->Next==NULL;
}
pNode NewNode(int key)
{
	pNode node=(pNode)malloc(sizeof(Node));
	if(node!=NULL)
	{
	    node->key=key;
    	node->Next=NULL;
	}
	return node;
}

void PushFront(pNode head,int key)
{
	assert(head);
	if((head->Next)==NULL)
	{
		head->Next=NewNode(key);
		return;
	}
	pNode newnode=NewNode(key);
	newnode->Next=head->Next;
	head->Next=newnode;
}
void PopFront(pNode head,int * key)
{
	assert(head);
	if(head==NULL)
	{
		return;
	}
	pNode del=head->Next;
	*key=del->key;
	head->Next=del->Next;
	free(del);
}
void Destroy(pNode head)
{
	assert(head);
    pNode cur=head;
	pNode del=NULL;
	while(cur!=NULL)
	{
		del=cur;
		cur=cur->Next;
        free(del);
	}
	head->Next=NULL;
}


void *Productor(void *arg)
{
	int key=0;
	while(1)
	{
		pthread_mutex_lock(&lock);
		key=rand()%101;
		PushFront(head,key);
		printf("Productor push %d\n",key);
		pthread_cond_signal(&cond);
		pthread_mutex_unlock(&lock);
		sleep(5);
	}
}
void *Customer(void *arg)
{
	int key=0;
	while(1)
	{
		pthread_mutex_lock(&lock);
		while(head->Next==NULL)
		{
			printf("waiting for date\n");
            pthread_cond_wait(&cond,&lock);
		}
		PopFront(head,&key);
		printf("Customer pop %d\n",key);
		pthread_mutex_unlock(&lock);
		//sleep(1);
	}
}
int main()
{

    head=(pNode)malloc(sizeof(Node));
	Initlinklist(&head);
	pthread_t t1,t2;
	srand(time(NULL));
	pthread_create(&t1,NULL,Productor,NULL);
	pthread_create(&t2,NULL,Customer,NULL);
	Destroy(head);
	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
    pthread_mutex_destroy(&lock);
	pthread_cond_destroy(&cond);
	return 0;

}
 我實現的是消費者快,而生產者慢的實例,所以當生產者生成一個,消費者拿走之後,其餘四秒鐘消費者都在等待生產者生產。

(6)POSIX信號量

      POSIX信號量和SystemV信號量作用相同,都是用於同步操作,達到無衝突的訪問共享資源的目的,單POSIX可以用於線程間同步。  

       初始化信號量:

        #include<semaphore.h>

        int sem_init(sem_t  *sem, int pshared,unsigned int value);

       參數: pshared  0表示線程間共享,非0表示進程間共享;默認爲0;

                  value  信號量初始值;

       銷燬信號量:

        int  sem_destroy(sem_t  *sem);

        等待信號量:

         int sem_wait(sem_t  *sem);   等待信號量,若是等待成功信號量的值減1,相當於P操作;

        發佈信號量:

        int   sem_post(sem_t *sem);  發佈信號量,表示資源使用完畢,可以歸還資源了,將信號量值加1;

       上邊的消費者生產者模型是基於鏈表的,其空間可以等他分配,現在寫一個基於固定大小的循環隊列重寫消費者生產者模型。(循環結構可以用數組+模運算實現)

#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#define SIZE 1024
//環形隊列
int arr[SIZE] = {0};
sem_t sem_pro;      //描述環形隊列中的空位置
sem_t sem_con;      //描述喚醒隊列中的數據
//生產者,只要環形隊列有空位,便不斷生產
void*productor(void*arg){
    int data = 0;
    int proIndex = 0;
    while(1){
        //有空位便生產,沒空位便阻塞等消費者消費
        sem_wait(&sem_pro);
        data = rand()%1234;
        arr[proIndex] = data;
        printf("product done %d\n",data);
        proIndex = (proIndex+1)%SIZE;
        //供消費者消費的數據加1
        sem_post(&sem_con);
    }
}
//消費者,只要環形隊列中有數據,就不斷消費
void*consumer(void*arg){
    int data = 0;
    int conIndex = 0;
    while(1){
        //環形隊列中存在數據則消費,不存在數據則阻塞,直到有數據爲止
        sem_wait(&sem_con);
        data = arr[conIndex];
        printf("consume done %d\n",data);
        conIndex = (conIndex+1)%SIZE;
        //最後,消費了一個數據,空位加1
        sem_post(&sem_pro);
    }
}
 
int main(){
    pthread_t pro,con;
    sem_init(&sem_pro,0,SIZE-1);        //一開始有很多空位置
    sem_init(&sem_con,0,0);         //但並沒有數據
 
    pthread_create(&pro,NULL,productor,NULL);
    pthread_create(&con,NULL,consumer,NULL);
    pthread_join(pro,NULL);
    pthread_join(con,NULL);
 
    sem_destroy(&sem_pro);
    sem_destroy(&sem_con);
    return 0;
}

 以上實現的都是單消費者單生產者mo





















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章