【Linux】多線程編程

目錄

一. 線程概述

1.爲什麼會有線程?

2.什麼是線程

3.進程與線程

4.進程VS線程

5.爲什麼要線程

6.線程函數是由誰提供的?

7.如何使用

二. 多線程程序設計

1.線程創建——pthread_create()

線程ID獲取/比相同——pthread_self()/ pthread_equal()

2.線程中止

主動——pthread_exit()+ return

被動——pthread_cancel()

註冊線程退出處理函數 —— pthread_cleanup_push() + pthread_cleanup_pop()

3.線程等待——pthread_join()

4.線程分離——pthread_detach()

注意事項

三. 線程同步 

1.互斥鎖(量)

初始化——pthread_mutex_init()

操作函數——pthread_mutex_lock/trylock/unlock()

銷燬——pthread_mutex_destroy()

 例子.

2.信號量

創建——sem_init()

信號量控制——sem_post() sem_wait()

 信號量銷燬——sem_destroy()

3.條件變量(用的不算多)

初始化條件變量 —— pthread_cond_init() 或 PTHREAD_COND_INITIALIZER

使用條件變量 —— pthread_cond_wait()  pthread_cond_signal()

 刪除條件變量

例子

小結

面試要點



一. 線程概述

1.爲什麼會有線程?

進程實現多任務的缺點

  • 進程間切換的計算機資源開銷很大,切換效率非常低(OS通過虛擬內存機制實現進程間的獨立,操作的都是虛擬內存)
  • 進程間數據共享(也就是通信方式,切內核什麼的)的開銷也很大

2.什麼是線程

線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬於一個進程的其他的線程共享進程擁有的全部資源。

3.進程與線程

進程與線程的關係

  • 線程是進程的一個執行單元,是進程內的調度實體。比進程更小的獨立運行的基本單位。線程也被稱爲輕量級進程
  • 同一進程的線程共享本進程的地址空間(一個進程生產的線程數量有限),而進程之間則是獨立的地址空間。
  • 進程退出,進程中所有線程全部退出;
  • 一個進程崩潰後,不會對其他進程產生影響,但是一個線程崩潰整個進程都死掉。所以多進程要比多線程健壯。
  • 線程不可能完全替代進程

Linux進程創建一個新線程時,線程將擁有自己的棧(因爲線程有自己的局部變量),但與它的創建者共享全局變量、文件描述符、信號句柄和當前目錄狀態。
 
Linux通過fork創建子進程與創建線程之間是有區別的:fork創建出該進程的一份拷貝,這個新進程擁有自己的變量和自己的PID,它的時間調度是獨立的,它的執行幾乎完全獨立於父進程。
 
進程可以看成一個資源的基本單位,而線程是程序調度的基本單位,一個進程內部的線程之間共享進程獲得的時間


4.進程VS線程

進程更關注進程間通信,線程更關注資源的保護

多線程比多進程成本低,但性能更低

  • 多進程是立體交通系統,雖然造價高,上坡下坡多耗點油,但是不堵車。
  • 多線程是平面交通系統,造價低,但紅綠燈太多,老堵車。

區別:

  • 進程是資源分配的最小單位,線程是任務調度的最小單位;
  • 每個進程擁有獨立的地址空間,多個線程共享進程地址空間
    • 線程之間切換比進程之間切換開銷少;
    • 線程的調度必須通過頻繁加鎖來保持同步,影響了線程併發性能;
    • 進程比線程更健壯,多進程之間相互獨立,進程的異常對其他進程無影響,一個線程的崩潰可能影響其他線程或者整個程序;
    • 線程之間的通信更方便(小數據量),同一進程下的線程共享全局變量、靜態變量等數據,而進程之間的通信需要以通信的方式(IPC)進行。不過如何處理好同步與互斥是編寫多線程程序的難點。
    • 多線程的代碼結構比多進程代碼結構易讀;

如何選擇:

  • 需要頻繁創建銷燬的優先用線程
  • 高性能交易服務器中間件,如TUXEDO,都是主張多進程的(核心功能用進程!不容易崩潰)
  • 需要進行大量計算的優先使用線程(節省進程的資源去做計算,如果進程做計算,進程本身就佔用很多資源了)
  • 強相關的處理用線程,弱相關的處理用進程(做一次就結束是弱相關,比如登錄註冊,要多次做的是強相關,比如文件傳輸)
  • 多機分佈的用進程,多核分佈的用線程

5.爲什麼要線程

線程的特點

  • 線程切換的開銷很低,實質是函數的切換
  • 線程通信機制簡單,使用全局變量(放主函數沒用)

1、和進程相比,它是一種非常"節儉"的多任務操作方式。在linux系統下,啓動一個新的進程必須分配給它獨立的地址空間,建立衆多的數據表來維護它的代碼段、堆棧段和數據段,這是一種"昂貴"的多任務工作方式。
 
2、運行於一個進程中的多個線程,它們之間使用相同的地址空間,而且線程間彼此切換所需時間也遠遠小於進程間切換所需要的時間。據統計,一個進程的開銷大約是一個線程開銷的30倍左右。
 
3、線程間方便的通信機制。對不同進程來說,它們具有獨立的數據空間,要進行數據的傳遞只能通過進程間通信的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由於同一進城下的線程之間貢獻數據空間,所以一個線程的數據可以直接爲其他線程所用,這不僅快捷,而且方便。
 
除以上優點外,多線程程序作爲一種多任務、併發的工作方式,還有如下優點:
1、使多CPU系統更加有效。操作系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。
2、改善程序結構。一個既長又複雜的進程可以考慮分爲多個線程,成爲幾個獨立或半獨立的運行部分,這樣的程序纔會利於理解和修改。
 

6.線程函數是由誰提供的?

不是操作系統,而是線程庫libpthread.a/.so,所以編譯程序時要加上-lpthread,不過線程庫在實現時,也是調用了相應的系統API的。不讓OS實現是爲了不要增加它的負擔。

庫的好處是可以跨平臺

7.如何使用

Linux系統下的多線程遵循POSIX線程接口,稱爲 pthread 。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時需要使用庫 libpthread.a.
如:
gcc main.c -lpthread -o main

 


二. 多線程程序設計

編譯時要加上 -lpthread

1.線程創建——pthread_create()

與創建進程不同,創建線程時可以指定一個工作函數,新線程將從這個函數開始執行,函數返回也就等價於線程退出
工作函數必須有一個(void *)型參數,新線程開始執行時,這個參數的值就是pthread_create函數的arg參數的值,因此可以利用它來向線程傳遞數據。
工作函數必須有(void *)型的返回值,它代表線程的退出狀態

#include <pthread.h>

int pthread_create(pthread_t *thread, pthread_attr_t *attr, 
                    void *(*start_routine)(void *), void *arg);

/*
   參數:
       thread:指向pthread_t類型的指針,用於引用新創建的線程。
       attr:用於設置線程的屬性,一般不需要特殊的屬性,所以可以簡單地設置爲NULL。
       *(*start_routine)(void *):傳遞新線程所要執行的函數地址。
       arg:新線程所要執行的函數的參數。

   返回值:
       調用如果成功,則返回值是0,如果失敗則返回錯誤代碼。
*/

例. 

#include <stdio.h>
#include <pthread.h>

int test = 0;

void *handle(void *argv)
{
	int num = *(int *)argv;
	while(1)
	{
		printf("thread argv = %d\n",num);
		printf("test = %d\n",test);
		sleep(1);
	}
}

int main()
{
	pthread_t threadid;
	int ret;
	int argc = 10;

    //創建線程(線程id,NULL,線程執行的函數,傳進函數的參數)
	ret = pthread_create(&threadid,NULL,handle,&argc);

	while(1)
	{
		printf("main loop...\n");
		test++;
		sleep(1);
	}
}

證明:多線程和主進程一起運行,並和主進程共享空間(test證明) 

線程ID獲取/比相同——pthread_self()/ pthread_equal()

include <pthread.h>

pthread_t pthread_self(void);

/*
   返回值:
       返回當前線程的ID
*/

include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

/*
   函數功能:
       可以使用pthread_equal()函數檢查兩個線程ID是否相同,如果相等則返回0  
*/

2.線程中止

主動——pthread_exit()+ return

 pthread_exit()

#include <pthread.h>

void pthread_exit(void *retval);

/*
   參數:
       retval:返回指針,指向線程向要返回的某個對象。一般寫NULL
               注意:絕不能用它返回一個指向局部變量的指針,因爲線程調用結束後,
                     這個局部變量就不存在了,這將引起嚴重的程序漏洞。
*/

調用pthread_exit()等價於在工作函數中執行return,區別是pthread_exit()可以在工作函數調用的任何函數中被調用。
如果主線程調用pthread_exit(),而不是exit()或執行return,則其他線程將繼續執行。

return

    return (void *)0;

pthread_exit()和return區別

  • pthread_exit()會彈出線程棧中所有的東西,把棧中保存的數據全部清空(pthread_cleanup_push)
  • return不會彈棧

 

被動——pthread_cancel()

有個bug:如果該被結束的線程裏面沒有產生系統調用(比如printf之類的函數,而是隻有i++,或什麼都沒有),那麼該函數對這種線程並沒有用,並無法終止該線程。

#include <pthread.h>
            
int pthread_cancel(pthread_t thread);

/*
   功能:
       當次線程是死循環時,可以調動這個函數主動取消該線程。此線程必須產生系統調用

   返回值:
       成功返回0,失敗返回非零錯誤號。
*/

 

註冊線程退出處理函數 —— pthread_cleanup_push() + pthread_cleanup_pop()

#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *), void *arg);

/*
   功能:
       將類型爲void (*routine)(void *)函數註冊爲“線程退出處理函數”,
       arg爲傳遞給退出處理函數的參數。註冊的原理就是將處理函數地址壓入
       線程棧。我們可以反覆調用該函數註冊多個退出處理函數,但是一般一個就夠了。
*/



void pthread_cleanup_pop(int execute);

/*
   功能:
       如果參數寫!0:會將壓入棧中的推出處理函數地址彈出,然後調用退出函數進行線程的掃尾處理。
       如果參數寫0:不彈出調用

*/

注意:

  •  如果註冊了多個線程退出處理函數的話,由於棧先進後出的特點,所以註冊壓棧的順序與彈棧調動的順序剛好相反。               
  • 兩個函數必須配對出現,有一個pthread_cleanup_push,就必須要對應有一個pthread_cleanup_pop,就算這個函數調用不到也必須寫,否者編譯時不通過。
  • 這就是調用return和pthread_exit退出線程的區別。pthread_cleanup_pop不論寫在exit的前面還是後面,線程結束後都會調用退出處理函數,pthread_cleanup_pop寫在return前面結束後會調用退出處理函數,但寫在return後面結束後不會調用退出處理函數。

彈棧線程退出處理函數的幾種條件:

  • 調用thread_cleanup_pop(!0),主動彈棧
  • 如果線程是被別人調用pthread_cancel取消的,也會彈棧
  • 如果線程是調用pthread_exit函數退出的,也會彈棧

例.

#include <stdio.h>
#include <pthread.h>

void thread_exit(void *arg)
{
	printf("my exit!\n");
}

void *print(void *arg)
{
	pthread_cleanup_push(thread_exit, NULL);

	printf("hh\n");
	sleep(2);

	//return (void *)0;
	pthread_exit(NULL);

	pthread_cleanup_pop(!0);
}

int main()
{
	pthread_t pid;

	pthread_create(&pid, NULL, print, NULL);

	pthread_join(pid, NULL);

	return 0;
}

3.線程等待——pthread_join()

等待的意思是,如果沒有等待,就算線程是個無限循環,main函數(主進程)不是循環,主進程結束線程就結束了,不會等着線程做完才結束。而有了這個函數就能等待線程結束再結束(應該是這樣?)。

子線程的資源靠父線程回收

線程等待的目的

  • 保證線程的退出順序:保證一個線程退出並且回收資源後允許下一個進程退出
  • 回收線程退出時的資源情況:保證當前線程退出後,創建的新線程不會複用剛纔退出線程的地址空間(同樣的空間你釋放了一次我又釋放了一次造成內存泄露)
  • 獲得新線程退出時的結果是否正確的退出返回值,這個有點類似回收殭屍進程的wait,保證不會發生內存泄露等問題

總結:保證線程資源能被回收

線程資源爲什麼不採用進程退出之後一起回收?

有些程序(進程)一旦運行後將會長期運行,不會結束,所以次線程在結束時必須回收資源,如果不回收,每結束一個次線程就導致一部分資源被佔用,慢慢累積會使得整個進程資源越用越少,最好導致進程崩潰,所以次線程結束時,必須回收次線程資源。舉例:1000個線程,600個已經執行完了。。。。。

#include <pthread.h>

int pthread_join(pthread_t th, void **thread_return);

/*
   參數:
       th:將要等待的線程,線程通過pthread_create返回的標識符來指定。
       thread_return:一個指針,指向另一個指針,而後者指向線程的返回值。不需要返回值則爲NULL
*/

 線程終止和等待的例子.

#include <stdio.h>
#include <pthread.h>

int flag = 0;
int ret1 = 1;
int ret2 = 2;

void *handle1(void *argv)
{
	int num = 1;
	while(1)
	{
		if(flag == 1)
		{
			printf("num = %d\n",num);
			num += 2;
			flag = 0;
			if(num == 19)
			{
				pthread_exit((void *)&ret1);
			}
		}
		
	}
}

void *handle2(void *argv)
{
	int num = 0;
	while(1)
	{
		if(flag == 0)
		{
			printf("num = %d\n",num);
			num += 2;
			flag = 1;
			if(num == 20)
			{
				pthread_exit((void *)&ret2);
			}
		}
		
	}
}

int main()
{
	pthread_t threadid1,threadid2;
	int ret;
	int argc = 10;
	void *thread_ret1;
	void *thread_ret2;

	ret = pthread_create(&threadid1,NULL,handle1,&argc);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;
	}

	ret = pthread_create(&threadid2,NULL,handle2,&argc);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;
	}

	
	pthread_join(threadid1, &thread_ret1);
	pthread_join(threadid2, &thread_ret2);

	printf("threadid1 is %d\n",*(int *)thread_ret1);
	printf("threadid2 is %d\n",*(int *)thread_ret2);


}

 

4.線程分離——pthread_detach()

線程的狀態決定了它的回收機制

  • 可結合態:這種狀態下的線程是能夠被其他進程回收其資源或殺死的,默認線程都是可結合態的
  • 分離態:這種狀態下的線程是不能夠被其他線程回收或殺死的;它的存儲資源在它終止時由系統自動釋放(這種更好一點)

如何避免多線程退出導致的內存泄漏?

  • 每個可結合線程需要顯示的調用pthread_join回收
  • 將其變成分離態的線程
#include <pthread.h>

int pthread_detach(pthread_t thread);

/*
   返回值:
       成功返回0。失敗返回錯誤值
*/

一個線程可以使用下面的方式分離自己:(可在自己的線程函數第一句就寫上)
   pthread_detach(pthread_self());
pthread_detach()不會導致調用者阻塞,也不會導致所操作的線程結束。如果調用pthread_detach()時線程已經結束,則清理其所佔用的資源。
 
對於創建時處於未分離狀態的線程,必須調用一次pthread_join()或pthread_detach(),否則線程結束後就會留下沒有釋放的資源。    

 

注意事項

  • 除了局部變量以外,所有其他變量都將在一個進程中的所有線程之間共享。
  •  線程沒有像進程那樣的父子關係,僅有屬於同一個進程的“同組”關係(進程實際上代表的是一個線程組)。
  •  在POSIX線程模型中,主線程可以創建一個新線程A,新線程A又可以創建另一個新線程B,線程A和B本身沒有父子關係,只是同屬於一個進程。
  •  等待線程結束的pthread_join()操作可以由任何一個同組的線程發起,不必是主線程。另外,如果主線程退出,即進程退出,則所有的線程也會隨之退出。  

 

三. 線程同步 

1.互斥鎖(量)

注意:互斥鎖只能在線程裏面用不能在進程裏面用。進程的互斥只能通過信號量來實現。

互斥鎖的變量一般定義在全局使用

使用步驟

  • 定義一個互斥鎖(變量)
  • 初始化互斥鎖:預設互斥鎖的初始值
  • 加鎖解鎖(解完鎖一般建議sleep1秒,反正鎖反覆被該程序獲得)
  • 進程退出時銷燬互斥鎖

初始化——pthread_mutex_init()

#include <pthread.h>

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

/*
   參數:
       mutex:指向要初始化的互斥量。互斥量用一個pthread_mutex_t型的變量表示。
       attr:指向一個描述互斥量屬性的結構體。attr參數可以爲NULL,表示使用默認屬性。
*/

操作函數——pthread_mutex_lock/trylock/unlock()

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

/*
   函數功能:
        pthread_mutex_lock():阻塞加鎖
                用於對mutex參數指向的互斥量進行加鎖。
                如果這時互斥量已經被鎖,則調用這個函數的線程將被阻塞,
                直到互斥量成爲未鎖狀態。函數返回時,表示這個互斥量已經
                變爲已鎖狀態,同時,函數的調用者成爲這個互斥量的擁有者。

        pthread_mutex_trylock():非阻塞加鎖
                也用於對mutex參數指向的互斥量進行加鎖。
                如果這時互斥量已經被鎖,則函數以錯誤狀態返回,而不是掛起等待

        pthread_mutex_unlock()用於對mutex參數指向的互斥量進行解鎖。如果
                 這時互斥量是未鎖狀態或不是當前線程所擁有的,則結果未定義。 
                 因此,互斥量必須在同一線程上成對出現。 
*/

銷燬——pthread_mutex_destroy()

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);   

/*
   函數功能:
       互斥量不用以後,應該使用下面的函數進行銷燬。
*/

 例子.

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

int flag = 0;
int ret1 = 1;
int ret2 = 2;

pthread_mutex_t mutex;

void *handle1(void *argv)
{

	pthread_mutex_lock(&mutex);
	printf("num1\n");
	sleep(1);
	pthread_mutex_unlock(&mutex);

}

void *handle2(void *argv)
{
	pthread_mutex_lock(&mutex);
	while(1)
	{
		printf("num2\n");
	}
	pthread_mutex_unlock(&mutex);
}


int main()
{
	pthread_t threadid1,threadid2;
	int ret;
	int argc = 10;
	void *thread_ret1;
	void *thread_ret2;

	pthread_mutex_init(&mutex, NULL);

	ret = pthread_create(&threadid1,NULL,handle1,&argc);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;
	}

	sleep(1);	//讓線程1一定優於線程2發生,不然一直進入線程2的死循環中

	ret = pthread_create(&threadid2,NULL,handle2,&argc);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;
	}


	while(1)
	{
	}

}

 

2.信號量

使用步驟

  • 定義信號量集合(直接定義sem_t類型的數組,幾個信號量數組裏就寫幾)
  • 初始化集合中的每個信號量
  • p、v操作
  • 進程結束時,刪除線程信號量集合

創建——sem_init()

#include <semaphore.h>

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

/*
   函數功能:
       初始化一個未命名的信號量(unnamed semaphore)。
   
   參數:
       sem:指向需要初始化的信號量(sem_t類型)。
       pshared:
                0:給線程使用
                !0:可以給進程使用(不穩定!不推薦用!)
                表明信號量是在一個進程的多個線程之間共享還是在多個進程之間共享。
                若pshared爲0,信號量被一個進程的多個線程共享,此時應該將信號量
                (sem_t)置於所有線程可見的位置(全局變量或動態分配)。
       value:指定信號量的初始值。

   返回值:
       執行成功返回0,出錯返回-1,並設置errno。
*/

 注意:初始化一個已經初始化了的信號量將導致未定義的行爲。

信號量控制——sem_post() sem_wait()

#include <semaphore.h>
 
int sem_wait(sem_t *sem);   // p
int sem_post(sem_t *sem);   // v


/*
   函數功能:
       sem_wait函數以原子操作的方式將信號量的值減1,但它會等待直到信號量
               有個非零值纔會開始減法操作。例如,對值爲2的信號量調用sem_wait,
               線程將繼續執行,但信號量的值會減到1。如果對值爲0的信號量調用
               sem_wait,這個函數就會等待,直到有其它線程增加了該信號量的值
               使其不再爲0爲止。
       sem_post的作用是以原子操作的方式給信號量的值加1
       sem_trywait:它是sem_wait的非阻塞版本。
*/

 信號量銷燬——sem_destroy()

#include <semaphore.h>

int sem_destroy(sem_t *sem);

/*
   函數功能:
       用完信號量後對它進行清理,清理該信號量所擁有的資源。如果你試圖
       清理的信號量正被一些線程等待,就會收到一個錯誤。

*/

例. 

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>


int flag = 0;
int ret1 = 1;
int ret2 = 2;

sem_t p,v;

void *handle1(void *argv)
{
	while(1)
	{
		sem_wait(&p);
		printf("num1\n");
		sleep(1);
		sem_post(&v);
	}
}

void *handle2(void *argv)
{
	while(1)
	{
		sem_wait(&v);
		printf("num2\n");
		sem_post(&p);
	}
}


int main()
{
	pthread_t threadid1,threadid2;
	int ret;
	int argc = 10;
	void *thread_ret1;
	void *thread_ret2;

	sem_init(&p, 0, 1);		//(p信號量地址,在多個線程間共享,初始值爲1)
	sem_init(&v, 0, 0);


	ret = pthread_create(&threadid1,NULL,handle1,&argc);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;
	}

	ret = pthread_create(&threadid2,NULL,handle2,&argc);
	if(ret < 0)
	{
		perror("pthread_create");
		return -1;
	}

	while(1)
	{
	}

}

 

3.條件變量(用的不算多)

作用:保證多線程協同工作。滿足條件線程就執行,不滿足就休眠,直到別的線程將條件準備好,然後通過條件變量將其喚醒。可以節約CPU的資源,提高利用率。

注意:條件變量需要在互斥鎖的配合下才能工作。

使用步驟:

  • 定義一個條件變量(全局變量pthread_cond_t類型)由於條件變量需要互斥鎖的配合,所以還需要定義一個線程互斥鎖。
  • 初始化條件變量
  • 使用條件變量
  • 刪除條件變量,也需要把互斥鎖刪除。

初始化條件變量 —— pthread_cond_init() 或 PTHREAD_COND_INITIALIZER

 方法一:

#include <pthread.h>
            
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

/*  
    功能:
        初始化條件變量,與互斥鎖的初始化類似。
        例.
        pthread_cond_t cond; //定義條件變量
        pthread_cond_init(&cond, NULL); //第二個參數爲NULL,表示不設置條件變量的屬性。          
                 
    返回值:成功返回0,失敗返回非零錯誤號
*/

方法二:

也可以直接初始化

    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//與互斥鎖的初始化的原理是一樣的

使用條件變量 —— pthread_cond_wait()  pthread_cond_signal()

等待條件的函數:裏面還要傳一個mutex鎖的變量,因爲要解鎖

#include <pthread.h>
                 
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 
          
/*     
    功能:
        檢測條件變量cond,如果cond沒有被設置,表示條件還不滿足,別人還沒有對cond進行設置,此時
        pthread_cond_wait會休眠(阻塞),直到別的線程設置cond表示條件準備好後,纔會被喚醒。
                    
    返回值:成功返回0,失敗返回非零錯誤號
                 
    參數
        cond:條件變量指針
        mutex:和條件變量配合使用的互斥鎖指針
*/

 pthread_cond_wait()的兄弟函數:

#include <pthread.h>

int pthread_cond_timedwait(pthread_cond_t *restrict cond, \
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
                    
/*
    功能:
        多了第三個參數,用於設置阻塞時間,如果條件不滿足時休眠(阻塞),但是不會一直休眠,
        當時間超時後,如果cond還沒有被設置,函數不再休眠。
*/

設置條件變量的函數

#include <pthread.h>
                 
int pthread_cond_signal(pthread_cond_t *cond);
                               
/*
    功能:
        當線程將某個數據準備好時,就可以調用該函數去設置cond,表示條件準備好了,
        pthread_cond_wait檢測到cond被設置後就不再休眠(被喚醒),線程繼續運行,
        使用別的線程準備好的數據來做事。
*/


int pthread_cond_broadcast(pthread_cond_t *cond);

/*
    功能:                    
        當調用pthread_cond_wait函數等待條件滿足的線程只有一個時,就是用pthread_cond_signal
        來喚醒,如果說有好多線程都調用pthread_cond_wait在等待時,它可以將所有調用            
        pthread_cond_wait而休眠的線程都喚醒。
*/

 刪除條件變量

也需要把互斥鎖刪除

#include <pthread.h>
                 
int pthread_cond_destroy(pthread_cond_t *cond);

例子

線程1給全局變量count++,值到5就有線程2輸出,再將count置爲0

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

pthread_mutex_t mutex;
pthread_cond_t cond;

int count = 0;

void *add(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		count++;
		if(count == 5)
		{
			pthread_cond_signal(&cond);
		}
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}

void *print(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		if(count != 5)
		{
			pthread_cond_wait(&cond, &mutex);
		}

		printf("count = %d\n", count);
		count = 0;

		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}

int main()
{
	pthread_t id1;
	pthread_t id2;

	int ret;

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

	ret = pthread_create(&id1, NULL, add, NULL);
	if(ret != 0)
	{
		perror("pthread_create error!");
		exit(1);
	}

	ret = pthread_create(&id2, NULL, print, NULL);
	if(ret != 0)
	{
		perror("pthread_create error!");
		exit(1);
	}

	pthread_join(id1, NULL);
	pthread_join(id2, NULL);

	pthread_cond_destroy(&cond);
	pthread_mutex_destroy(&mutex);

	return 0;
}

 

 


小結

爲什麼要用線程:進程的併發方式開銷比較大(創建開銷大,切換開銷大,通信機制開銷大)

線程進程關係

線程的特點

編譯的時候要連接線程庫

線程的創建

線程的退出:主動退出,被動退出,註冊線程的退出處理函數

線程的等待:目的:回收可結合狀態的線程的資源。也可以讓線程變成分離態。

線程狀態:不同狀態對應不同的回收機制,特點

面試要點

線程進程的區別,如何使用?

項目中,爲什麼這個部分用線程/進程?
 

 

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