Linux线程——线程创建和基本使用(多线程并发)
1.線程的概念與實現(xiàn)方式
1.1 線程的概念
概念:線程是進程內(nèi)部的一條執(zhí)行序列或執(zhí)行路徑,一個進程可以包含多條線程。線程是進行資源調(diào)度和分配的基本單位 。
(1)每個進程至少有一條執(zhí)行路徑,所以一個進程至少有一個線程。
(2)每個進程都有一個主線程。
1.2 線程的實現(xiàn)方式
在操作系統(tǒng)中,線程的實現(xiàn)有以下三種方式:
(1)用戶級線程:由線程庫中的代碼進行管理,處理 ,銷毀。用戶自己創(chuàng)建的多線程,即多個處理路徑,無法使用多處理器的資源,在內(nèi)核眼里就只是一條路徑。
(2)內(nèi)核級線程:由內(nèi)核直接創(chuàng)建、直接管理、直接調(diào)度,直接結(jié)束。開銷大,可以利用處理器的資源。
(3)組合級線程:內(nèi)核空間允許其使用多處理器的資源。比如用戶創(chuàng)建多個線程,內(nèi)核可以創(chuàng)建兩個線程來處理這些線程,以達到可以有效使用處理器資源的目的。
1.3Linux 中線程的實現(xiàn)
Linux 實現(xiàn)線程的機制非常獨特。從內(nèi)核的角度來說,它并沒有線程這個概念。Linux 把所有的線程都當(dāng)做進程來實現(xiàn)。內(nèi)核并沒有準(zhǔn)備特別的調(diào)度算法或是定義特別的數(shù)據(jù)結(jié)構(gòu)來表征線程。相反,線程僅僅被視為一個與其他進程共享某些資源的進程。每個線程都擁有唯一隸屬于自己的 task_struct,所以在內(nèi)核中,它看起來就像是一個普通的進程(只是線程和其他一些進程共享某些資源,如地址空間)。
1.4 進程與線程的區(qū)別
(1)進程是資源分配的最小單位,線程是 CPU 調(diào)度的最小單位。
(2)進程有自己的獨立地址空間,線程共享進程中的地址空間。
(3)進程的創(chuàng)建消耗資源大,線程的創(chuàng)建相對較小。
(4)進程的切換開銷大,線程的切換開銷相對較小。
2.線程基本使用
2.1 線程庫中的接口介紹
#include <pthread.h>/* pthread_create()用于創(chuàng)建線程 thread: 接收創(chuàng)建的線程的 ID attr: 指定線程的屬性//一般傳NULL start_routine:指定線程函數(shù) arg: 給線程函數(shù)傳遞的參數(shù) 成功返回 0, 失敗返回錯誤碼 */ int pthread_create(pthread_t * thread, const pthread_attr_t *attr,void *(*start_routine) ( void *),void *arg);/* pthread_exit()退出線程 retval:指定退出信息 */ int pthread_exit( void *retval);/* pthread_join()等待 thread 指定的線程退出,線程未退出時,該方法阻塞 retval:接收 thread 線程退出時,指定的退出信息 */ int pthread_join(pthread_t thread, void **retval);2.2 多線程代碼
如下簡單寫一個多線程代碼:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<assert.h> #include<pthread.h>void* pthread_fun(void* arg) {for(int i = 0; i < 5; i++){printf("fun run\n");sleep(1);} }int main() {pthread_t tid;int res = pthread_create(&tid,NULL,pthread_fun,NULL);assert(res == 0);for(int i = 0; i < 10; i++){printf("main run\n");sleep(1);}exit(0); }運行結(jié)果(注意編譯鏈接需要帶上庫 -lpthread):
如果子線程循環(huán)10次,主線程循環(huán)5次呢?代碼及運行結(jié)果如下:
運行結(jié)果:
發(fā)現(xiàn)主線程結(jié)束后,子線程并沒有打印完也緊跟著結(jié)束了。
所以,主線程不會因為其他線程的結(jié)束而結(jié)束,但是其它線程的結(jié)束會因為主線程的結(jié)束而結(jié)束。這是因為主線程結(jié)束后會退出進程,所以進程里的其他線程都會終止結(jié)束。所以為了正常運行程序,一般我們都會讓主線程等待其他線程結(jié)束后再結(jié)束。代碼如下:
運行結(jié)果:
其他線程沒有結(jié)束的話,主線程會在pthread_join()處阻塞。所以主線程會在其他線程結(jié)束之后再結(jié)束,程序正常退出。
2.3 線程并發(fā)運行
示例代碼 1:
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<pthread.h>void* pthread_fun(void* arg) {int index = *(int*)arg;int i = 0;for(; i < 5; i++){printf("index = %d\n",index);sleep(1);} }int main() {pthread_t id[5];int i = 0;for(; i < 5; i++){pthread_create(&id[i],NULL,pthread_fun,(void*)&i);}for(i = 0; i < 5; i++){pthread_join(id[i],NULL);}exit(0); }運行結(jié)果1:
運行結(jié)果2:
為什么會產(chǎn)生這種情況呢?線程并發(fā)問題。
這是因為我們向pthread_fun傳入i的地址。首先來說說為什么會出現(xiàn)多個線程拿到同一個i的值。線程創(chuàng)建在計算機中需要很多個步驟,我們進入for循環(huán)傳入i的地址后就去進行下一個for循環(huán),創(chuàng)建的線程還沒有從地址中獲取打印i的值,主函數(shù)就繼續(xù)創(chuàng)建后面的線程了,導(dǎo)致多個線程并發(fā),拿到同一個i值,而且不是創(chuàng)建該線程的時候i的值。
注意到打印第一個運行結(jié)果都是打印0,這是因為主函數(shù)第一個for循環(huán)已經(jīng)結(jié)束了,后面一個for循環(huán)將i又置為0,而這些線程在主函數(shù)第一個for循環(huán)執(zhí)行的時候,都沒有回獲取i的值打印,直到下一個for循環(huán),這些線程才獲取i值打印,所以打印出來 都是0。
示例代碼 2:多線程并發(fā)訪問同一塊內(nèi)存的問題
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<assert.h> #include<pthread.h>int g = 0;void* pthread_fun(void* arg) {for(int i = 0; i < 1000; i++){printf("g = %d\n",++g);}pthread_exit(NULL); }int main() {pthread_t id[5];for(int i = 0; i < 5; i++){pthread_create(&id[i],NULL,pthread_fun,NULL);}for(int j = 0; j < 5; j++){char* s = NULL;pthread_join(id[j],(void**)&s);}exit(0); }三次運行結(jié)果:
運行結(jié)果最后可能是5000,也可能是4900多,這是怎么回事呢?
看一下本人的虛擬機設(shè)置,處理器數(shù)量2個,每個處理器2個內(nèi)核。
原因就是linux的線程是內(nèi)核級線程。程序中對g++并不是原子操作,對g++,計算機需要 很多次操作 ,比如將內(nèi)存中的g讀取到寄存器中,再從寄存器中讀走進行++,再回頭進行寫入等等一系列操作。可能一個線程拿到了內(nèi)存中的g,還沒來得及++再寫回去,另一個線程被分配到另一個處理器上,讀取了相同值的g進行++。所以我們得到的值有時候會比5000要小。
解決方法有:
(1)將處理器設(shè)置為單核處理器;
(2)進行線程同步。
總結(jié)
以上是生活随笔為你收集整理的Linux线程——线程创建和基本使用(多线程并发)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 回溯法——旅行售货员问题
- 下一篇: Linux线程——线程同步