Linux 線程的概念和使用

本文主要介紹Linux線程的概念以及線程的模型,並介紹基於POSIX線程的接口。


什麼是線程?

  • 在一個程序裏的一個執行路線就叫做線程(thread)。更準確的定義是:線程是“一個進程內部的控制序列”。
  • 一切進程至少都有一個執行線程。

進程與線程

  • 進程是資源分配與競爭的基本單位
  • 線程是程序執行的最小單位
  • 線程共享進程數據,但也有自己的一部分數據
  • 線程ID
  • 一組寄存器:IP,PSW,堆棧指針
  • errno
  • 信號狀態
  • 優先級

Linux 線程的概念和使用

 

  •  

fork和創建新線程的區別

  • 當一個進程執行一個fork調用的時候,會創建出進程的一個新拷貝,新進程將擁有它自己的變量和它自己的PID。這個新進程的運行時間是獨立的,它在執行時幾乎完全獨立於創建它的進程。
  • 在進程裏面創建一個新線程的時候,新的執行進程會擁有自己的堆棧(因此也就有自己的局部變量),但要與他的創建者共享全局變量、文件描述符、信號處理器和當前工作目錄狀態。

線程的優點

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

線程的缺點

  • 性能損失
  • 一個很少被外部事件阻塞的計算密集型線程往往無法與其他線程共享同一個處理器。如果計算密集型線程的數量比可用的處理器多,那麼可能會有較大的性能損失,這裏的性能損失指的是增加了額外的同步和調度開銷,而可用的資源不變。
  • 健壯性降低
  • 編寫多線程需要更全面更深入的考慮,在一個多線程程序裏,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良的可能性是很大的,換句話說線程之間是缺乏保護的。
  • 缺乏訪問控制
  • 進程是訪問控制的基本粒度,在一個線程中調用某些OS函數會對整個進程造成影響。
  • 編程難度提高
  • 編寫與調試一個多線程比單線程程序困難得多

線程調度競爭範圍

  • 操作系統提供了各個模型,用來調度應用程序創建的線程。這些模型時間的主要不同是:在競爭系統資源(特別是CPU時間)時,線程調度競爭範圍(thread-scheduling contention scope)不一樣
  • 進程競爭範圍(Process contention scope):各個線程在同一個進程競爭“被調度的CPU時間”(但不直接和其他進程中的線程競爭)。
  • 系統競爭範圍(System contention scope):線程直接和”系統範圍“內的其他線程競爭。

線程模型

N:1用戶線程模型

  • “線程實現”建立在“進程控制”機制之上,由用戶空間的程序庫來管理。OS內核完全不知道線程信息。這些線程成爲用戶空間線程。
  • 這些線程都工作在“進程競爭範圍”
  •  
  • 在N:1線程模型中,內核不干涉線程的任何生命活動,也不干涉同一進程中的線程環境切換
  • 在N:1線程模型中,一個進程中的多個線程只能調度到一個CPU,這種約束限制了可用的並行總量。
  • 第二個缺點是如果某個線程執行了一個“阻塞式”操作(如read),那麼,進程中的所有線程都會阻塞,直至那個操作結束。爲此,一個線程的實現是爲這些阻塞式函數提供包裝器,用非阻塞版本替換這些系統調用,以清除這種限制。

1:1核心線程模型

  • 在1:1核心線程模型中,應用程序創建的每一個線程都由一個核心線程直接管理。
  • OS內核將每一個核心線程都調度到系統CPU上,因此,所有線程都工作在“系統競爭範圍”。
  • 這種線程的創建於調度由內核完成,因爲這種線程的系統開銷比較大(但一般來說,比進程開銷小)

Linux 線程的概念和使用

 


N:M混合線程模型

  • N:M混合線程模式提供了兩級控制,將用戶線程映射爲系統的可調度體以實現並行,這個可調度體稱爲輕量級進程(LWP:lightweight process),LWP再一一映射到核心線程。

Linux 線程的概念和使用

 

 


POSIX線程庫

pthread_create函數

  • 功能:創建一個新的線程
  • 原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
  • 參數:
  • thread:返回線程ID
  • attr:設置線程的屬性,attr爲NULL表示使用默認屬性
  • start_routine:是個函數地址,線程啓動後要執行的函數
  • arg:傳給線程啓動函數的參數
  • 返回值:成功返回0,失敗返回錯誤碼

pthread_join函數

  • 功能:等待線程結束
  • 原型:
int pthread_join(pthread_t thread, void **retval);
  • 參數:
  • thread:線程ID
  • retval:它指向一個指針,後者指向線程的返回值
  • 參數:成功返回0,失敗返回錯誤碼

pthread_exit函數

  • 功能:線程終止
  • 原型:
void pthread_exit(void *value_ptr);
  • 參數:
  • value_ptr:value_ptr不要指向一個局部變量。
  • 返回值:無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)

pthread_self函數

  • 功能:返回線程ID
  • 原型:
pthread_t pthread_self(void);
  • 返回值:成功返回0

pthread_cancel

  • 功能:取消一個執行的線程
  • 原型:
int pthread_cancel(pthread_t thread);
  • 參數:
  • thread:線程ID
  • 返回值:成功返回0;失敗返回錯誤碼

pthread_detach函數

  • 功能:將一個線程分離
  • 原型:
int pthread_detach(pthread_t thread);
  • 參數:
  • thread:線程ID
  • 返回值:成功返回0;失敗返回錯誤碼

實例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define ERR_EXIT(m)	\
	do {	\
			perror(m);	\
			exit(EXIT_FAILURE);	\
	} while (0)
void *thread_routine(void *arg) {
	int i;
	for (i=0; i<20; ++i) {
		printf("B");
		fflush(stdout);
		usleep(20);
		if (i == 3) {
			pthread_exit("ABC");
		}
	}
	return 0;
}
int main() {
	pthread_t tid;
	int ret;
	if ((ret = pthread_create(&tid, NULL, thread_routine, NULL)) != 0) {
		fprintf(stderr, "pthread_create: %s\n", strerror(ret));
		exit(EXIT_FAILURE);
	}
	int i;
	for (i=0; i<20; ++i) {
		printf("A");
		fflush(stdout);
		usleep(20);
	}
	
	void *value;
	if ((ret = pthread_join(tid, &value)) != 0) {
		fprintf(stderr, "pthread_join: %s\n", strerror(ret));
		exit(EXIT_FAILURE);
	}
	printf(" msg = %s\n", (char *)value);
	return 0;
}

線程屬性

初始化與銷燬屬性

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

獲取與設置分離屬性

int pthread_attr_getdetachstate(const pthread_attr_t \*attr, int \*detachstate);
int pthread_attr_setdetachstate(const pthread_attr_t *attr, int detachstate);

獲取與設置棧大小

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t \*attr, size_t \*stacksize);

獲取與設置棧溢出保護區大小

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);

獲取與設置線程競爭範圍

int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(pthread_attr_t *attr, int *scope);

獲取與設置調度策略

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);

獲取與設置繼承的調度策略

int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);

獲取與設置調度參數

int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param);

獲取與設置併發級別

int pthread_setconcurrency(int new_level);
int pthread_getconcurrency(void);
  • 僅在N:,線程模型中有效,設置併發級別,給內核一個提示:表示提供給定級別數量的核心線程來映射用戶線程是有效的。

 


線程特定數據

  • 在單線程程序中,我們經常要用到全局變量以實現多個函數間共享數據
  • 在多線程環境下,由於數據空間是共享的,因此全局變量也爲所有線程所共有
  • 但有時應用程序設計中有必要提供線程的私有的全局變量,僅在某個線程中有效,但卻可以跨多個函數訪問
  • POSIX線程庫通過維護一定的數據結構來解決這個問題,這些數據成爲(Thread-sepcific Data,或TSD)
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;

實例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define ERR_EXIT(m)	\
	do {	\
			perror(m);	\
			exit(EXIT_FAILURE);	\
	} while (0)
typedef struct tsd {
	pthread_t tid;
	char *str;
} tsd_t;
pthread_key_t key_tsd;
pthread_once_t once_control = PTHREAD_ONCE_INIT;
void destroy_routine(void *value) {
	printf("destroy\n");
	free(value);
}
void init_routine() {
	pthread_key_create(&key_tsd, destroy_routine);
}
void *thread_routine(void *arg) {
 pthread_once(&once_control, init_routine);
	tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t));
	
	tsd_t *value = (tsd_t *)malloc(sizeof(tsd_t));
	value->tid = pthread_self();
	value->str = (char *)arg;
	pthread_setspecific(key_tsd, value);
	printf("%s setspecific %p\n", (char *)arg, value);
	value = pthread_getspecific(key_tsd);
	printf("tid=0x%x str=%s\n", value->tid, value->str);
	sleep(2);
	value = pthread_getspecific(key_tsd);
	printf("tid=0x%x str=%s\n", value->tid, value->str);
	return NULL;
}
int main() {
	// pthread_key_create(&key_tsd, destroy_routine);
	pthread_t tid1;
	pthread_t tid2;
	int ret;
	if ((ret = pthread_create(&tid1, NULL, thread_routine, "thread1")) != 0) {
		fprintf(stderr, "pthread_create: %s\n", strerror(ret));
		exit(EXIT_FAILURE);
	}
	if ((ret = pthread_create(&tid2, NULL, thread_routine, "thread2")) != 0) {
		fprintf(stderr, "pthread_create: %s\n", strerror(ret));
		exit(EXIT_FAILURE);
	}
	pthread_join(tid1, NULL);
	pthread_join(tid1, NULL);
	pthread_key_delete(key_tsd);
	return 0;
}

需要c/c++ Linux服務器高階知識視頻資料的朋友可以點擊鏈接加入羣聊【linux後臺服務架構交流】

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