文章目錄
- 一、什么是線程?
- 1.1 Linux 中線程該如何理解?
- 1.2 如何理解把資源分配給線程?
- 1.2.1 虛擬地址到物理地址的轉換
- 1.3 線程 VS 進程
- 1.3.1 線程為什么比進程更輕量化?
- 1.3.2 線程的優點
- 1.3.3 線程缺點
- 1.3.4 線程異常
- 1.3.5 線程用途
- 1.3.6 線程和進程
- 二、線程控制
- 2.1 pthread_create——創建一個線程
- 2.2 全局變量在線程間是共享的
- 2.3 pthread_join——線程等待
- 2.4 pthread_exit——終止一個線程
- 2.5 pthread_cancel——取消一個線程
- 2.6 全局函數可以被多個線程同時調用
- 2.7 線程函數的參數和返回值可以傳遞對象
- 2.8 C++11 的線程庫
- 2.9 pthread_self——獲取線程ID
- 2.10 再來理解 `pthread` 線程庫
- 2.11 創建一批線程
- 2.12 驗證——每個線程都有自己獨立的棧結構
- 2.13 線程之間沒有秘密
- 2.14 線程的局部存儲
- 2.15 pthread_detach——線程分離
- 三、結語
一、什么是線程?
線程是進程內的一個執行分支,線程的執行粒度,要比進程細。
1.1 Linux 中線程該如何理解?
地址空間是進程的資源窗口。在 Linux
中,線程在進程”內部“執行,即線程在進程的地址空間內運行,任何執行流要執行,都要有資源(代碼,數據,CPU資源);在 Linux
中,線程的執行粒度要比進程更細,即線程執行進程代碼的一部分;在 Linux
中,復用進程數據結構和管理算法來描述和組織線程,Linux
中沒有真正意義上的線程,即在 Linux
中沒有為線程創建獨屬于自己的 TCB
結構體(thread ctrl block
) ,而是用”進程“的內核數據結構(PCB
)模擬的線 程;CPU 只有執行流的概念,所以從原則上來說,CPU 是不區分進程和線程的,但是 Linux
操作系統要區分進程和線程;我們把 Linux
中的執行流叫做輕量級進程。
線程:我們認為線程是操作系統調度的基本單位。
進程:進程是承擔分配系統資源(線程(執行流資源)、地址空間、頁表、物理內存)的基本實體。
1.2 如何理解把資源分配給線程?
1.2.1 虛擬地址到物理地址的轉換
站在地址空間角度,線程分配資源本質就是分派地址空間范圍。
1.3 線程 VS 進程
1.3.1 線程為什么比進程更輕量化?
- 創建和釋放更加輕量化,創建線程只需要常見
PCB
對象就行。(生死) - 切換更加輕量化(運行)。同一個進程內的多個線程在切換的時候,不需要更新
CPU
中的cache
緩存、進程地址空間、頁表等。只需要更新少量的上下文數據。
創建線程不能給該線程重新申請時間片,而是將線程的時間片劃分部分給線程。
cat /proc/cpuinfo
:查看 CPU
的信息。
1.3.2 線程的優點
-
創建一個新線程的代價要比創建一個新進程小的多。
-
與進程之間的切換相比,線程之間的切換需要操作系統做的工作要少很多。
-
線程占用的資源要比進程少很多。
-
能充分利用多處理器的可并行數量。
-
在等待慢速 I/O 操作結束的同時,程序可執行其他的計算任務。
-
計算密集型應用,為了能在多處理器系統上運行,將計算分解到多個線程中實現。
-
I/O 密集型應用,為了提高性能,將 I/O 操作重疊。線程可以同時等待不同的 I/O 操作。
1.3.3 線程缺點
-
性能損失:一個很少被外部事件阻塞的計算密集型線程往往無法與其它線程共享同一個處理器,如果計算密集型線程的數量比可用的處理器多,那么可能會有較大的新能損失(切換浪費時間),這里的性能損失指的是增加了額外的同步和調度開銷,而可用的資源不變。
-
健壯性降低:編寫多線程需要更全面深入的考慮,在一個線程程序里,因時間分配上的細微偏差或因共享了不該共享的變量而造成不良影響的可能性是很大的,換句話說就是線程之間缺乏安全保護。
-
缺乏訪問控制:進程是訪問控制的基本粒度,在一個線程中調用某些 OS 函數會對整個進程造成影響。
-
編程難度提高:編寫與調試一個多線程程序比單線程程序困難的多。
1.3.4 線程異常
-
單個線程如果出現除零,野指針問題導致線程崩潰,進程也會隨著崩潰。
-
線程是進程的執行分支,線程出異常,就類似進程出異常,進而觸發信號機制,終止進程,進程終止,該進程內的所有線程也就隨即退出。
1.3.5 線程用途
-
合理的使用多線程,能提高
CPU
密集型程序的執行效率。 -
合理的使用多線程,能提高 I/O 密集型程序的用戶體驗(如一邊寫代碼一邊下載開發工具,就是多線程運行的一種表現)
1.3.6 線程和進程
-
進程是資源分配的基本單位。
-
線程是調度的基本單位。
-
線程共享進程數據,但也擁有自己的一部分數據:
-
線程 ID
-
一組寄存器(線程的上下文)
-
棧
-
errno
-
信號屏蔽子
-
調度優先級
-
進程的多個線程之間共享同一地址空間,因此代碼段、數據段都是共享的,如果定義一個函數,在各線程中都可以的調用,如果定義一個全局變量,在各線程中都可以訪問到,除此之外,各個線程還共享以下進程資源和環境:
- 文件描述符表
- 每種信號的處理方式(
SIG_IGN
、SIG_DFL
或者自定義的信號處理函數) - 當前工作目錄
- 用戶
id
和組id
二、線程控制
Linux
的內核中,沒有很明確的線程概念,只有輕量級進程的概念。所以 Linux
操作系統不會給我們直接提供線程的系統調用,只會給我們提供輕量級進程的系統調用。偉大的 Linux
程序員將輕量級進程的接口進行封裝,給用戶在應用層開發出來了一個 pthread
線程庫。幾乎所有的 Linux
平臺都是默認自帶這個庫的,Linux
中編寫多線程代碼,需要使用第三方 pthread
庫。
2.1 pthread_create——創建一個線程
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
-
thread
:輸出型參數,返回線程 ID -
attr
:設置線程的屬性,為NULL
表示使用默認屬性 -
start_routine
:是個函數地址,線程啟動后要執行的函數 -
arg
:傳給啟動線程的參數 -
返回值:創建成功返回0;創建失敗返回對應的錯誤碼
#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;void *threadRoutine(void *args)
{while(true){cout << "new thread, pid: " << getpid() << endl;sleep(2);}
}int main()
{pthread_t pid;pthread_create(&pid, nullptr, threadRoutine, nullptr);while(true){cout << "main thread, pid: " << getpid() << endl;sleep(1);}return 0;
}
在編譯線程代碼的時候,需要加上 -lpthread
選項。因為 pthread.h
是第三方庫,但是 g++
編譯器僅僅可以找到 pthread.h
和該庫的位置,并不會默認幫我們去鏈接 pthread
庫,因此我們要加 -lpthread
選項,告訴 g++
編譯器,我們要鏈接這個庫。
ps -aL
:其中 L
表示查看當前操作系統中的所有輕量級線程。
LWP
:一個輕量級進程的 ID,CPU 是按照 LWP 來進行調度的。
CPU 調度的基本單位是線程,PID == LWP
的線程叫做主線程。
2.2 全局變量在線程間是共享的
#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;int g_val = 100;void *threadRoutine(void *args)
{const char *name = (const char*)args;while (true){printf("%s, pid: %d, g_val: %d, &g_val: 0X%p\n", name, getpid(), g_val, &g_val);sleep(1);}
}int main()
{pthread_t pid;pthread_create(&pid, nullptr, threadRoutine, (void *)"Thread 1");while (true){printf("main thread, pid: %d, g_val: %d, &g_val: 0X%p\n", getpid(), g_val, &g_val);sleep(1);g_val++;}return 0;
}
2.3 pthread_join——線程等待
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);
thread
:要等待的線程 IDretval
:輸出型參數,獲取線程函數的返回值- **返回值:**等待成功0被返回;等待失敗,錯誤碼被返回
線程等待的目的:
-
防止新線程內存泄露
-
主線程獲取子線程的執行結果
#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;int g_val = 100;void *threadRoutine(void *args)
{const char *name = (const char*)args;int cnt = 5;while (true){printf("%s, pid: %d, g_val: %d, &g_val: 0X%p\n", name, getpid(), g_val, &g_val);sleep(1);cnt--;if(cnt == 0) break;}return (void *)100;
}int main()
{pthread_t pid;pthread_create(&pid, nullptr, threadRoutine, (void *)"Thread 1");// while (true)// {// printf("main thread, pid: %d, g_val: %d, &g_val: 0X%p\n", getpid(), g_val, &g_val);// sleep(1);// g_val++;// }void *ret;pthread_join(pid, &ret);cout << "main thread quit..., Thread 1 return val: " << (long long int)ret << endl;return 0;
}
線程執行完它的函數后就退出了,主線程在等待的時候,默認是阻塞等待。主線程等待子線程,只能獲取到子線程執行函數的返回值,不考慮子線程出異常,因為子線程一旦出異常,主線程也會跟著遭殃。
exit 是用來終止進程的,不能用來直接終止線程。任何一個子線程在任何地方調用 exit
都表示整個進程退出。
2.4 pthread_exit——終止一個線程
pthread_exit
:終止調用該函數的線程
#include <pthread.h>void pthread_exit(void *retval);
retval
:線程函數的返回值
#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;int g_val = 100;void *threadRoutine(void *args)
{const char *name = (const char*)args;int cnt = 5;while (true){printf("%s, pid: %d, g_val: %d, &g_val: 0X%p\n", name, getpid(), g_val, &g_val);sleep(1);cnt--;if(cnt == 0) break;}pthread_exit((void *)200);return (void *)100;
}int main()
{pthread_t pid;pthread_create(&pid, nullptr, threadRoutine, (void *)"Thread 1");void *ret;pthread_join(pid, &ret);cout << "main thread quit..., Thread 1 return val: " << (long long int)ret << endl;return 0;
}
注意:pthread_exit
或者 return
返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。
2.5 pthread_cancel——取消一個線程
pthread_cancel
:取消一個已存在的目標線程。
#include <pthread.h>int pthread_cancel(pthread_t thread);
-
thread
:要取消的進程 ID -
返回值:取消成功返回0;取消失敗,對應的錯誤碼被返回
#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;int g_val = 100;void *threadRoutine(void *args)
{const char *name = (const char*)args;int cnt = 5;while (true){printf("%s, pid: %d, g_val: %d, &g_val: 0X%p\n", name, getpid(), g_val, &g_val);sleep(1);cnt--;if(cnt == 0) break;}pthread_exit((void *)200);return (void *)100;
}int main()
{pthread_t pid;pthread_create(&pid, nullptr, threadRoutine, (void *)"Thread 1");sleep(1);pthread_cancel(pid);void *ret;pthread_join(pid, &ret);cout << "main thread quit..., Thread 1 return val: " << (long long int)ret << endl;return 0;
}
一個進程被取消,它的返回值是 PTHREAD_CANCELED
一個宏:
2.6 全局函數可以被多個線程同時調用
#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;int g_val = 100;void Print(const string &name)
{printf("%s is running, pid: %d, g_val: %d, &g_val: 0X%p\n", name.c_str(), getpid(), g_val, &g_val);
}void *threadRoutine(void *args)
{const char *name = (const char*)args;int cnt = 5;while (true){Print(name);// 調用全局函數sleep(1);cnt--;if(cnt == 0) break;}pthread_exit((void *)200);return (void *)100;
}int main()
{pthread_t pid;pthread_create(&pid, nullptr, threadRoutine, (void *)"Thread 1");int cnt = 0;while (true){Print("main thread");// 調用全局函數sleep(1);g_val++;cnt++;if(cnt == 10) break;}void *ret;pthread_join(pid, &ret);cout << "main thread quit..., Thread 1 return val: " << (long long int)ret << endl;return 0;
}
這說明代碼區對所有的線程來說是共享的。
2.7 線程函數的參數和返回值可以傳遞對象
一個求和任務
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <cstdlib>using namespace std;class Request
{
public:Request(int start, int end, const string &threadname):start_(start),end_(end),threadname_(threadname){}int sum(){int ret = 0;for(int i = start_; i <= end_; i++){cout << threadname_ << " is running..." << endl;ret += i;usleep(10000);}return ret;}public:int start_; // 起始數int end_; // 終止數string threadname_; // 線程的名字
};class Response
{
public:Response(int result, int exitcod):result_(result),exitcode_(exitcod){}
public:int result_; // 計算結果int exitcode_; // 標記結果的可靠性
};void *SumCount(void *args)
{Request *rq = static_cast<Request *>(args);Response *rp = new Response(rq->sum(), 0);delete rq;return rp;
}int main()
{pthread_t tid;// 創建一個線程Request *rq = new Request(1, 100, "Thread 1");pthread_create(&tid, nullptr, SumCount, rq);void *ret;pthread_join(tid, &ret); // 線程等待,獲取線程的返回值Response *rp = static_cast<Response *>(ret);cout << "result: " << rp->result_ << ", exitcode: " << rp->exitcode_ << endl;delete(rp);return 0;
}
該實例證明了堆空間也是共享的。
2.8 C++11 的線程庫
pthread.h
是原生線程庫。C++11 的線程庫本質上是封裝了原生線程庫。在 Linux
下,C++11 的線程庫底層封裝的是 Linux
的系統調用,在 Windows
下,C++11 底層封裝的是 Windows
的系統調用。這也是 C++ 語言具有跨平臺性的體現。如果代碼中直接使用系統調用,那么就不具有跨平臺性。
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <thread>using namespace std;void threadrun()
{while(true){cout << "I am a new thread for C++" << endl;sleep(1);}
}int main()
{thread th(threadrun);// 創建一個線程th.join();return 0;
}
2.9 pthread_self——獲取線程ID
返回調用該函數的線程 ID。
#include <pthread.h>pthread_t pthread_self(void);
- 返回值:該函數始終會調用成功,返回調用該函數線程的 ID
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <thread>
#include <string>using namespace std;string toHex(int num) // 轉十六進制接口
{char ret[64];snprintf(ret, sizeof(ret), "%p", num);return ret;
}void *threadroutine(void *args)
{while(true){sleep(2);cout << "thread id: " << toHex(pthread_self()) << endl;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadroutine, nullptr);while(true){cout << "creat a new thread, id: " << toHex(tid) << endl;sleep(1);}pthread_join(tid, nullptr);return 0;
}
2.10 再來理解 pthread
線程庫
Linux
內核中沒有很明確線程的概念,只有輕量級進程的概念,clone
接口就是用來創建一個輕量級進程。pthread_creat
底層就是封裝了 clone
。
#include <sched.h>int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, .../* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
線程的概念是 pthread
線程庫給我們提供的,我們在使用原生線程庫的時候,g++
默認使用動態鏈接,所以該庫是會被加載到內存中的,然后通過頁表映射到進程的共享區中,線程肯定不止一個,所以線程庫一定要把當前操作系統中創建的所有線程管理起來,管理的方式就是通過先描述再組織,因此在線程庫中一定存在一個描述線程的結構體,將該結構體稱作 TCB
,一個線程的 tid
(上文中提到的線程 ID)就是其在線程庫中的 TCB
對象的起始地址(改地址是一個虛擬地址)。這個 tid
是用戶層面的,給用戶來使用的,LWP
是內核中的概念,因為 CPU
調度的最小單位是線程(也就是輕量級進程),所以操作系統需要有一個編號來唯一標識一個線程。在Linux
中,我們所說的線程是用戶級線程,因為在 Linux
中,線程的概念是 pthread
為我們提供的,在 Windows
中的線程是內核級線程,因為它是由操作系統直接提供的。在 Linux
中一個用戶級線程對應一個內核級線程。
每個線程在被創建出來之后,都要有自己獨立的棧結構,因為每個線程都有自己的調用鏈,執行流的本質就是調用鏈,該棧空間會保存一個執行流在運行過程中產生的臨時變量,函數調用進行的入棧操作。主線程直接使用地址空間中為我們提供的棧結構即可,其他子線程的獨立棧,都在共享區,具體來說是在 pthread
庫中,tid
指向的 TCB
中維護了該線程的獨立棧。
2.11 創建一批線程
#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <unistd.h>using namespace std;#define NUM 10class ThreaInfo
{
public:ThreaInfo(const string &threadname):threadname_(threadname){}public:string threadname_;
};string toHex(pthread_t tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "%p", tid);return buffer;
}void *threadroutine(void *args)
{int i = 0;ThreaInfo *ti = static_cast<ThreaInfo*>(args);while(i < 10){cout << ti->threadname_.c_str() << " is running, tid: " << toHex(pthread_self()) << ", pid: " << getpid() << endl;i++;sleep(1);}return nullptr;
}int main()
{vector<pthread_t> tids;for(int i = 0; i < NUM; i++){pthread_t tid;ThreaInfo *ti = new ThreaInfo("Thread-"+to_string(i));pthread_create(&tid, nullptr, threadroutine, ti);tids.push_back(tid);sleep(1);}for(auto tid:tids){pthread_join(tid, nullptr);}return 0;
}
2.12 驗證——每個線程都有自己獨立的棧結構
#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <unistd.h>using namespace std;#define NUM 3class ThreaInfo
{
public:ThreaInfo(const string &threadname):threadname_(threadname){}public:string threadname_;
};string toHex(pthread_t tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "%p", tid);return buffer;
}void *threadroutine(void *args)
{int i = 0;int num = 0;ThreaInfo *ti = static_cast<ThreaInfo*>(args);while(i < 10){cout << ti->threadname_.c_str() << " is running, tid: " << toHex(pthread_self()) << ", pid: " << getpid() << ", num: " << num << ", &num: " << toHex((pthread_t)&num) << endl;i++;num++;usleep(10000);}return nullptr;
}int main()
{vector<pthread_t> tids;for(int i = 0; i < NUM; i++){pthread_t tid;ThreaInfo *ti = new ThreaInfo("Thread-"+to_string(i));pthread_create(&tid, nullptr, threadroutine, ti);tids.push_back(tid);// sleep(1);usleep(1000);}for(auto tid:tids){pthread_join(tid, nullptr);}return 0;
}
每個線程都去調用了 threadroutine
函數,但是每個線程都有自己的 num
,都是從0開始,并且 num
的地址都不同。這正是因為每個線程都有自己獨立的棧結構,每個線程在調用該函數時,都將該函數中的局部變量壓入自己所在的棧空間。
2.13 線程之間沒有秘密
雖然每一個線程都有自己獨立的棧結構,但是對于同一個進程創建的多個線程來說,它們都是在該進程的地址空間中,所以只要你想,一個進程是可以拿到另一個線程棧中的數據。
定義一個全局的指針變量,讓其指向線程1棧空間中的一個變量,這樣就能在主線程中去獲取子線程棧空間的數據
#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <unistd.h>using namespace std;#define NUM 3int *p = nullptr;class ThreaInfo
{
public:ThreaInfo(const string &threadname):threadname_(threadname){}public:string threadname_;
};string toHex(pthread_t tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "%p", tid);return buffer;
}void *threadroutine(void *args)
{int i = 0;int num = 0;ThreaInfo *ti = static_cast<ThreaInfo*>(args);if(ti->threadname_ == "Thread-1") p = # // 將線程1中的 num 變量的地址存到 p 指針里面while(i < 10){cout << ti->threadname_.c_str() << " is running, tid: " << toHex(pthread_self()) << ", pid: " << getpid() << ", num: " << num << ", &num: " << &num << endl;i++;num++;usleep(10000);}return nullptr;
}int main()
{vector<pthread_t> tids;for(int i = 0; i < NUM; i++){pthread_t tid;ThreaInfo *ti = new ThreaInfo("Thread-"+to_string(i));pthread_create(&tid, nullptr, threadroutine, ti);tids.push_back(tid);// sleep(1);usleep(1000);}cout << "main thread get Thread-1 num: " << *p << ", &num: " << p << endl;for(auto tid:tids){pthread_join(tid, nullptr);}return 0;
}
雖然這樣做可以,但是我們在代碼中是禁止這樣做的。
2.14 線程的局部存儲
定義的普通全局變量是被所有線程所共享的,如果想讓該全局變量在每個線程內部,各自私有一份,可以在定義全局變量的前面加上 __thread
,這并不是語言給我們提供的,而是編譯器給我們提供。并且 __thread
只能用來修飾內置類型,不能用來修飾自定義類型。
#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <unistd.h>using namespace std;#define NUM 3int *p = nullptr;__thread int val = 100;class ThreaInfo
{
public:ThreaInfo(const string &threadname):threadname_(threadname){}public:string threadname_;
};string toHex(pthread_t tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "%p", tid);return buffer;
}void *threadroutine(void *args)
{int i = 0;// int num = 0;ThreaInfo *ti = static_cast<ThreaInfo*>(args);// if(ti->threadname_ == "Thread-1") p = # // 將線程1中的 num 變量的地址存到 p 指針里面while(i < 10){cout << ti->threadname_.c_str() << " is running, tid: " << toHex(pthread_self()) << ", pid: " << getpid() << ", val: " << val << ", &num: " << &val << endl;i++;// num++;val++;usleep(10000);}return nullptr;
}int main()
{vector<pthread_t> tids;for(int i = 0; i < NUM; i++){pthread_t tid;ThreaInfo *ti = new ThreaInfo("Thread-"+to_string(i));pthread_create(&tid, nullptr, threadroutine, ti);tids.push_back(tid);// sleep(1);usleep(1000);}// cout << "main thread get Thread-1 num: " << *p << ", &num: " << p << endl;for(auto tid:tids){pthread_join(tid, nullptr);}return 0;
}
此時 val
雖然定義在全局,但實際上在每一個進程的獨立棧中間中都會為 val
開辟一塊空間,來進行存儲。
2.15 pthread_detach——線程分離
- 默認情況下,新創建的線程是
joinable
的,線程退出后,需要對其進行pthread_join
操作,否則無法釋放資源,從而造成資源泄露。 - 如果主線程不關心子線程的返回值,
join
是一種負擔,這個時候,我們可以告訴操作系統,當線程退出時,自動釋放線程資源。
#include <pthread.h>int pthread_detach(pthread_t thread);
-
thread
:要分離的線程 ID -
返回值:分離成功返回0;失敗錯誤碼被返回
-
小Tips:該函數可以由主線程來調用,也可以由子線程來調用。
#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <cstring>using namespace std;#define NUM 3int *p = nullptr;// __thread int val = 100;class ThreaInfo
{
public:ThreaInfo(const string &threadname):threadname_(threadname){}public:string threadname_;
};string toHex(pthread_t tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "%p", tid);return buffer;
}void *threadroutine(void *args)
{int i = 0;// int num = 0;ThreaInfo *ti = static_cast<ThreaInfo*>(args);// if(ti->threadname_ == "Thread-1") p = # // 將線程1中的 num 變量的地址存到 p 指針里面while(i < 10){cout << ti->threadname_.c_str() << " is running, tid: " << toHex(pthread_self()) << ", pid: " << getpid() << endl;i++;// num++;// val++;usleep(10000);}return nullptr;
}int main()
{vector<pthread_t> tids;for(int i = 0; i < NUM; i++){pthread_t tid;ThreaInfo *ti = new ThreaInfo("Thread-"+to_string(i));pthread_create(&tid, nullptr, threadroutine, ti);tids.push_back(tid);// sleep(1);usleep(1000);}// cout << "main thread get Thread-1 num: " << *p << ", &num: " << p << endl;for(auto tid:tids){pthread_detach(tid); // 線程分離}for(auto tid:tids){int ret = pthread_join(tid, nullptr);printf("%p: ret: %d, messg: %s\n", tid, ret, strerror(ret));}return 0;
}
子線程各自只執行了一次,是因為,子線程在被創建出來之后,主線程立即將所有的子線程進行了分離,然后,主線程又去進行 join
,此時因為所有的子線程已經被分離了,主線程去 join
就不會阻塞等待了,而是直接出錯返回,最后主線程執行完畢就直接退出了,主線程退出,也就意味著進程退出,進程退出所有的資源就要被釋放,所有子線程賴以生存的環境也沒了,所以子線程也就跟著沒了。因此,線程分離后,要保證主線程最后退出,常見的情況是主線程跑死循環,一直不退出。
線程分離本質上是線程 TCB
中的一個屬性,pthread_detach
本質上就是去修改改屬性。
三、結語
今天的分享到這里就結束啦!如果覺得文章還不錯的話,可以三連支持一下,春人的主頁還有很多有趣的文章,歡迎小伙伴們前去點評,您的支持就是春人前進的動力!