Linux多線程編程


前言:有這樣一道面試題(來自http://blog.csdn.net/morewindows/article/details/7392749):

      “編寫一個程序,開啟3個線程,這3個線程的ID分別為ABC,每個線程將自己的ID在屏幕上打印10遍,要求輸出結果必須按ABC的順序顯示;如:ABCABC.依次遞推。”

我們就從這樣一題出發,認識多線程,了解其同步機制,最后正確解答這一類題目。本文框架如下:

  • 進程與線程
  • 多線程的優越性
  • 線程基本函數
  • 多線程同步
  • 題目代碼

  

一.進程與線程

      進程的定義:進程是為了描述程序在並發執行時對系統資源的共享,所需的一個描述程序執行時動態特征的概念。進程是具有獨立功能的程序關於某個數據集合上的一次運行活動,是系統進行資源分配、調度和保護的獨立單位。

      線程的定義:線程也成為輕量級進程,是進程中的一個運行實體,作為CPU的調度單位。一個進程由多個線程組成,線程與同屬一個進程的其他的線程共享進程所擁有的全部資源。

      同一進程內的所有線程除了共享全局變量外還共享:

  • 進程指令;
  • 大多數數據;
  • 打開的文件(即描述符);
  • 信號處理函數和信號處置;
  • 當前工作目錄;
  • 用戶ID和組ID。

     每個線程擁有各自的:

  • 線程ID;
  • 寄存器集合,包括程序計數器和棧指針;
  • 棧(用於存放局部變量和返回地址);
  • errno;
  • 信號掩碼;
  • 優先級。

      結構上的不同可以讓我們更加了解進程和線程的相異之處:

      (1)進程是資源分配的基本單位;線程與資源分配無關,它屬於某一個進程,並與進程內的其他線程共享進程的資源。

      (2)當進程發生調度時,不同的進程擁有不同的虛擬地址空間,而同一進程內的不同線程共享同一地址空間。

      (3)線程只由先關堆棧(系統棧或用戶棧)寄存器和線程控制塊組成。寄存器用來存儲線程內的局部變量,但不能存儲其他線程的相關變量。

      (4)進程切換時涉及有關資源指針的保存和地址空間的變化;線程切換時,由於處於同一進程內,所以不涉及資源信息的保存和地址空間的變化,從而減少了操作系統的時間開銷。

 

二.多線程的優越性

      在傳統的UNIX模型中,當一個進程需要另一個實體來完成某事,它就fork一個子進程並讓子進程去處理。但是fork的調用有如下缺點:

      (1)fork的代價是昂貴的。fork要把父進程的內存印象復制到子進程,並在子進程中復制所有描述符等。

      (2)fork返回之后父子進程之間信息的傳遞需要進程通信機制。調用fork之前父進程向尚未存在的子進程傳遞信息相當容易,因為子進程將從父進程數據空間及所有描述符的一個副本開始運行,但是從子進程向父進程返回信息卻比較費力。

      針對這兩點,多線程技術相應而生,它具有如下優越性:

      (1)它是一種非常"節儉"的多任務操作方式。而運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。

      (2)線程間方便的通信更加方便。由於同一進程下的線程之間共享數據空間,所以一個線程的數據可以直接為其它線程所用,這不僅快捷,而且方便。但是,同一進程內的所有線程共享相同的全局內存,這樣線程之間的通信就變得相當簡單,隨之而來的就是同步問題

 

三.基本線程函數

1.pthread_create函數,創建線程

int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);

      pthread_t *tid:一個進程內的各個線程是由線程ID標識的,如果新線程創建成功,返回tid指針。

      const pthread_attr_t *attr:每個線程有多個屬性,包括優先級、初始棧大小、是否是一個守護線程等等。

      void *(*func)(void *):線程啟動函數,線程從調用這個函數開始,或顯示結束(調用pthread_exit()),或隱式結束(讓該函數返回)。

      void *arg:線程執行func函數的傳遞參數。

2.pthread_join函數,等待一個線程終止

int pthread_join(pthread_t *tid, void **status);

      void **status:二級指針,如果status指針非空,那么所等待線程的返回值將存放在status指向的位置。

3.pthread_self函數,返回線程ID

int pthread_self(void);

      跟進程比較,相當於getpid。

4.pthread_detach函數,線程分離

int pthread_detach(pthread_t tid);  

      線程或者是可匯合的(joinable),或者是脫離的(detach)。當可匯合的線程終止時,線程ID和退出狀態將保留,知道另外一個線程調用pthread_join。脫離的線程終止時,釋放所有的資源,因此我們不能等待它終止。若要一個線程知道另一個線程的終止時間,我們就要保留第二個線程的可匯合性。

5.pthread_exit函數,線程終止

int pthread_exit(void **status);  

      若線程未脫離,那么它的線程ID和退出狀態將保留到另外一個線程調用pthread_join為止。

 

四.多線程的同步

      有了上面的基本函數還不足以完成本題的要求,為什么呢?因為題目要求按照ABCABC...的方式打印,而3個線程卻在搶占資源,所以無法控制排列順序。這時就需要用到多線程編程中的同步技術。

      對於多線程編程來說,同步就是同一時間只允許一個線程訪問資源,而其他線程不能訪問。多線程有3種同步方式:

  • 互斥鎖
  • 條件變量
  • 讀寫鎖

1.互斥鎖

      互斥鎖是最基本的同步方式,它用來保護一個“臨界區”,保證任何時刻只由一個線程在執行其中的代碼。這個“臨界區”通常是線程的共享數據

      下面三個函數給一個互斥鎖上鎖和解鎖:

int pthread_mutex_lock(pthread_mutex_t *mptr);

int pthread_mutex_trylock(pthread_mutex_t *mptr);

int pthread_mutex_unlock(pthread_mutex_t *mptr);

  假設線程2要給已經被線程1鎖住的互斥鎖(mutex)上鎖(即執行pthread_mutex_lock(mutex)),那么它將一直阻塞直到到線程1解鎖為止(即釋放mutex)。

      如果互斥鎖變量時靜態分配的,通常初始化為常值PTHREAD_MUTEX_INITIALIZER,如果互斥鎖是動態分配的,那么在運行時調用pthread_mutex_init函數來初始化。

2.條件變量

      互斥鎖用於上鎖,而條件變量則用於等待,通常它都會跟互斥鎖一起使用。

int pthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);

  通常pthread_cond_signal只喚醒等待在相應條件變量上的一個線程,若有多個線程需要被喚醒呢,這就要使用下面的函數了:

int pthread_cond_broadcast(pthread_cond_t *cptr);

3.讀寫鎖

      互斥鎖將試圖進入連你姐去的其他簡稱阻塞住,而讀寫鎖是將讀和寫作了區分,讀寫鎖的分配規則如下:

      (1)只要沒有線程持有某個給定的讀寫鎖用於寫,那么任意數目的線程可以持有該讀寫鎖用於讀;

      (2)僅當沒有線程持有某個給定的讀寫鎖用於讀或用於寫時,才能分配該讀寫鎖用於寫。

int pthread_rwlock_rdlock(pthread_relock_t *rwptr);
int pthread_rwlock_wrlock(pthread_relock_t *rwptr);
int pthread_rwlock_unlock(pthread_relock_t *rwptr);

  

五.題目代碼

      分析此題:

1.主線程main創建3個線程tid0,tid1,tid2;

2.設一個全局變量num,互斥鎖mutex保護此臨界區保證每次只有一個線程訪問num;

3.若搶占到資源的線程tid並不是我們需要的,那么讓它阻塞;

4.若搶占到資源的線程tid正好是我們需要的,那么就打印相應字母;

5.解鎖,喚醒其他兩個等待線程;

6.main函數等待3個線程打印結束才結束。

      代碼如下:

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

int num=0;

static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void *func(void *);

int main()
{
	pthread_t tid[3];
	int ret=0,i;
	for(i=0;i<3;i++)
		if((ret=pthread_create(&tid[i],NULL,func,(void*)i))!=0)
			printf("create thread_%c error\n",i+'A');
   	for(i=0;i<3;i++)
		pthread_join(tid[i],NULL);
	printf("\n");
	return 0;
}

void *func(void *argc)
{
	int i;
	for(i=0;i<10;i++)
	{
		pthread_mutex_lock(&mutex);
	    while(num!=(int)argc)
			pthread_cond_wait(&cond,&mutex);
		printf("%c",num+'A');
		num=(num+1)%3;
		pthread_mutex_unlock(&mutex);
		pthread_cond_broadcast(&cond);
	}
	pthread_exit(0);
}

 

 

參考資料:

1. 《UNIX網絡編程卷2》

2.http://www.cnblogs.com/skynet/archive/2010/10/30/1865267.html

3.http://www.cnblogs.com/vamei/archive/2012/10/09/2715393.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM