歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux多線程同步之互斥量和條件變量

1. 什麼是互斥量

互斥量從本質上說是一把鎖,在訪問共享資源前對互斥量進行加鎖,在訪問完成後釋放互斥量上的鎖。對互斥量進行加鎖以後,任何其他試圖再次對互斥量加鎖的線程將會被阻塞直到當前線程釋放該互斥鎖。如果釋放互斥鎖時有多個線程阻塞,所以在該互斥鎖上的阻塞線程都會變成可進行狀態,第一個變成運行狀態的線程可以對互斥量加鎖,其他線程在次被阻塞,等待下次運行狀態。

pthread_mutex_t 就是POSIX對於mutex的實現。

函數名 參數 說明 pthread_mutex_init

pthread_mutex_t * mutex,

constpthread_mutex_t *attr 初始化一個互斥量,靜態方式可以直接使用PTHREAD_MUTEX_INITIALIZER進行賦值初始化 pthread_mutex_destroy pthread_mutex_t *mutex 釋放對互斥變量分配的資源。注意pthread_mutex_init有可能malloc了資源 pthread_mutex_lock pthread_mutex_t *mutex 如果互斥量已經上鎖,調用線程阻塞直至互斥量解鎖 pthread_mutex_trylock pthread_mutex_t *mutex 加鎖,如果失敗不阻塞 pthread_mutex_unlock pthread_mutex_t *mutex 解鎖 使用init函數進行初始化:
  1. #include
  2.  
  3. pthread_mutex_tfoo_mutex;
  4.  
  5. voidfoo()
  6. {
  7. pthread_mutex_init(&foo_mutex,NULL);
  8. pthread_mutex_lock(&foo_mutex);
  9. /*Dowork.*/
  10. pthread_mutex_unlock(&foo_mutex);
  11. pthread_mutex_destroy(&foo_mutex);
  12. }

    當然該初始化

    1. pthread_mutex_init(&foo_mutex,NULL);
      只能foo_mutex使用前初始化一次,最後destroy。初始化已經初始化的mutex將導致undefined behavior。

      另外一種用法:

      1. pthread_mutex_tfoo_mutex=PTHREAD_MUTEX_INITIALIZER;
      2. voidfoo()
      3. {
      4. pthread_mutex_lock(&foo_mutex);
      5. /*Dowork.*/
      6. pthread_mutex_unlock(&foo_mutex);
      7. }
        當然了,這兩種用法都有問題:如果在lock住後unlock之前出現exception,那麼這個鎖永遠也不能unlock。這種情況下需要guard這個資源。具體可參照boost::mutex::scoped_lock的實現,非常簡單但是極大簡化了mutex的安全使用。

        2. 什麼是條件變量

        與互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發生為止。通常條件變量和互斥鎖同時使用。

        條件變量使我們可以睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。

        條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,並釋放等待狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量可以被用來實現這兩進程間的線程同步。

        條件變量的初始化和mutex的初始化差不多,也是有兩種方式:

        pthread_cond_tmy_condition=PTHREAD_COND_INITIALIZER;

        也可以利用函數pthread_cond_init動態初始化。

        下面中各個函數的簡介。

        函數名 參數 說明 pthread_cond_init pthread_cond_t *cond,
        const pthread_condattr_t *attr 初始化 pthread_cond_destroy pthread_cond_t *cond 回收 pthread_cond_wait pthread_cond_t *cond,
        pthread_mutex_t *mutex 等待,無超時 pthread_cond_timedwait pthread_cond_t *cond,pthread_mutex_t *mutex,
        const struct timespec *abstime 等待,有超時 pthread_cond_signal pthread_cond_t *cond 一個在相同條件變量上阻塞的線程將被解鎖。如果同時有多個線程阻塞,則由調度策略確定接收通知的線程 pthread_cond_broadcast pthread_cond_t *cond 將通知阻塞在這個條件變量上的所有線程。一旦被喚醒,線程仍然會要求互斥鎖。

         

        一個簡單使用條件變量進行線程同步的小例子:

        /*
         * pthread_mutex_cond.c
         *
         */
        
        #include 
        #include 
        #include 
        
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
        pthread_cond_t cond =  PTHREAD_COND_INITIALIZER;
        int i = 0;
        
        void *thread1(void *);
        void *thread2(void *);
        
        int main(void)
        {
        	pthread_t tid1, tid2;
        	if(pthread_create(&tid1, NULL, thread1, NULL))
        		exit(1);
        	if(pthread_create(&tid2, NULL, thread2, NULL))
        		exit(1);
        	if(pthread_join(tid1, NULL))
        		exit(1);
        	printf("thread1 exit\n");
        	if(pthread_join(tid2, NULL))
        		exit(1);
        	printf("thread2 exit\n");
        
        	pthread_mutex_destroy(&mutex);
        	pthread_cond_destroy(&cond);
        	exit(0);
        }
        
        void *thread1(void *arg)
        {
        	printf("thread1 start\n");
        	while(i <= 6)
        	{
        		pthread_mutex_lock(&mutex);
        		printf("thread1: lock %d\n", __LINE__);
        		printf("thread1 i = %d\n", i);
        		if(i%3 == 0)
        		{
        			printf("thread1:signal 1  %d\n", __LINE__);
        			pthread_cond_signal(&cond);
        			printf("thread1:signal 2  %d\n", __LINE__);
        		}
        		pthread_mutex_unlock(&mutex);
        		printf("thread1: unlock %d\n", __LINE__);
        		sleep(1);	//sleep 1s,讓線程2得以執行
        		i++;
        	}
        	pthread_exit((void *)0);
        }
        
        void *thread2(void *arg)
        {
        	//sleep(1);
        	printf("thread2 start\n");
        	while(i <= 6)
        	{
        		pthread_mutex_lock(&mutex);
        		printf("thread2: lock %d\n", __LINE__);
        		printf("thread2 i = %d\n", i);
        		if(i%3 != 0)
        		{
        			 printf("thread2: wait 1  %d\n", __LINE__);
        			 pthread_cond_wait(&cond, &mutex);
        			 printf("thread2: wait 2  %d\n", __LINE__);
        		}
        
        		pthread_mutex_unlock(&mutex);
        		printf("thread2: unlock %d\n", __LINE__);
        		sleep(1);	//sleep 1s,讓線程1得以執行
        	}
        	pthread_exit((void *)0);
        }
        
        運行結果:

        thread2 start
        thread2: lock 65
        thread2 i = 0
        thread2: unlock 75
        thread1 start
        thread1: lock 42
        thread1 i = 0
        thread1:signal 1 46
        thread1:signal 2 48
        thread1: unlock 51
        thread2: lock 65
        thread2 i = 0
        thread2: unlock 75
        thread1: lock 42
        thread1 i = 1
        thread1: unlock 51
        thread2: lock 65
        thread2 i = 1
        thread2: wait 1 69
        thread1: lock 42
        thread1 i = 2
        thread1: unlock 51
        thread1: lock 42
        thread1 i = 3
        thread1:signal 1 46
        thread1:signal 2 48
        thread1: unlock 51
        thread2: wait 2 71
        thread2: unlock 75
        thread2: lock 65
        thread2 i = 3
        thread2: unlock 75
        thread1: lock 42
        thread1 i = 4
        thread1: unlock 51
        thread2: lock 65
        thread2 i = 4
        thread2: wait 1 69
        thread1: lock 42
        thread1 i = 5
        thread1: unlock 51
        thread1: lock 42
        thread1 i = 6
        thread1:signal 1 46
        thread1:signal 2 48
        thread1: unlock 51
        thread2: wait 2 71
        thread2: unlock 75
        thread2: lock 65
        thread2 i = 6
        thread2: unlock 75
        thread1 exit
        thread2 exit

        生產者-消費者的實現

        該問題描述了兩個共享固定大小緩沖區的線程——即所謂的“生產者”和“消費者”——在實際運行時會發生的問題。 要求:當工作隊列為空時,消費者不能從工作隊列取走數據,直到工作隊列有數據時才可以。 本例中用一個單鏈表來模擬工作隊列,生產者生產一個結構體串在鏈表的表頭上,消費者從表頭取走結構體。在兩個線程裡分別計數,生產者生產6次,消費者消費6次。
        /*
         * producer-consumer.c
         *
         */
        
        #include 
        #include 
        #include 
        
        struct msg{
        	struct msg *next;
        	int num;
        };
        
        struct msg *head;	//共享資源,全局指針初始化為NULL
        pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
        
        void *consumer(void *);
        void *producer(void *);
        
        int main(void)
        {
        	pthread_t tid1,tid2;
        	int res;
        
        	srand(time(NULL));
        	res = pthread_create(&tid1,NULL,producer,NULL);
        	if(res != 0)
        	{
        		perror("thread producer create failed\n");
        		exit(1);
        	}
        	res = pthread_create(&tid2,NULL,consumer,NULL);
        	if(res != 0)
        	{
        		perror("thread consumer create failed\n");
        		exit(1);
        	}
        	pthread_join(tid1,NULL);
        	if(res != 0)
        	{
        		perror("join thread producer failed\n");
        		exit(1);
        	}
        	printf("thread producer exit\n");
        	pthread_join(tid2,NULL);
        	if(res != 0)
        	{
        		perror("join thread consumer failed\n");
        		exit(1);
        	}
        	printf("thread consumer exit\n");
        
        	pthread_mutex_destroy(&mutex);
        	pthread_cond_destroy(&has_product);
        	exit(0);
        }
        
        void *producer(void *arg)
        {
        	struct msg *mp;
        	int i;
        	printf("producer thread start\n");
        	for(i=0;i<6;i++)
        	{
        		printf("producer i = %d\n", i);
        		mp = (struct msg *)malloc(sizeof(struct msg));
        		mp->num = rand()%100 + 1;
        		printf("Produce %d\n", mp->num);
        		pthread_mutex_lock(&mutex);
        		mp->next = head;
        		head = mp;	//生產者生產一個結構體串在鏈表的表頭上
        		pthread_mutex_unlock(&mutex);
        		pthread_cond_signal(&has_product);
        		sleep(1);	//讓另一個線程有機會執行
        	}
        	pthread_exit(NULL);
        }
        
        void *consumer(void *arg)
        {
        	struct msg *con;
        	int i;
        	printf("consumer thread start\n");
        	for(i=0;i<6;i++)
        	{
        		printf("consumer i = %d\n", i);
        		pthread_mutex_lock(&mutex);
        		while(head == NULL)
        		{
        			printf("struct msg is null\n");
        			pthread_cond_wait(&has_product,&mutex);
        		}
        		con = head;	//消費者從表頭取走結構體
        		head = con->next;
        		pthread_mutex_unlock(&mutex);
        		printf("Consume %d\n", con->num);
        		free(con);
        		sleep(1);	//讓另一個線程有機會執行
        	}
        	pthread_exit(NULL);
        }
        
        運行結果:

         

        consumer thread start
        consumer i = 0
        struct msg is null
        producer thread start
        producer i = 0
        Produce 52
        Consume 52
        consumer i = 1
        struct msg is null
        producer i = 1
        Produce 33
        Consume 33
        consumer i = 2
        struct msg is null
        producer i = 2
        Produce 77
        Consume 77
        consumer i = 3
        struct msg is null
        producer i = 3
        Produce 86
        Consume 86
        consumer i = 4
        struct msg is null
        producer i = 4
        Produce 84
        Consume 84
        consumer i = 5
        struct msg is null
        producer i = 5
        Produce 46
        Consume 46
        thread producer exit
        thread consumer exi
        t

Copyright © Linux教程網 All Rights Reserved