? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?🎬慕斯主頁:修仙—別有洞天
?? ????????????????????????????????????????? ???今日夜電波:どうして (feat. 野田愛実)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 0:44━━━━━━?💟──────── 3:01
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????🔄 ? ?? ? ? ? ?? ? ????
??????????????????????????????????????💗關注👍點贊🙌收藏您的每一次鼓勵都是對我莫大的支持😍
目錄
如何創建線程?
pthread_self()
如何終止線程?
通過return nullptr來線程終止
通過pthread_exit()來線程終止
通過pthread_cancel()來取消線程(先看后面的等待在回頭看這里)
線程等待
pthread_join()
pthread_detach()
一個小拓展
線程id詳解
pthread庫知識補充
clone()
系統調用問題
如何理解pthread庫來管理線程
前面提到的LWP為什么和pthread_self()獲得的不同?
線程局部存儲是啥?
__thread
線性局部存儲示例
如何創建線程?
????????在Linux中,可以使用POSIX線程庫(pthread)來創建線程。pthread_create()
函數是用于創建線程的函數。它定義在<pthread.h>
頭文件中,其聲明如下:
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
? ? pthread_create()
函數的參數具體含義如下:
- pthread_t *thread:這是一個指向
pthread_t
類型的指針,用于存儲新創建線程的ID。在調用pthread_create()
時,可以傳遞一個pthread_t
類型的指針變量,或者直接傳遞某個pthread_t
類型變量的地址。- const pthread_attr_t *attr:這個參數是一個指向
pthread_attr_t
類型的指針,用于設置線程的屬性。如果設置為NULL,則使用默認屬性創建線程。- void *(*start_routine) (void *):這是一個函數指針,指向新線程將要執行的函數。這個函數通常被稱為線程函數,它應該接受一個
void *
類型的參數,并返回一個void *
類型的值。- void *arg:這是傳遞給線程函數的參數,可以是任意類型的指針。
? ? pthread_create()
函數在成功時返回0,失敗時返回錯誤號。如果成功創建了線程,新線程將從start_routine
指定的函數開始執行。
????????例子:
#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <time.h>
#include <unistd.h>
#include <pthread.h>// typedef std::function<void()> func_t;
using func_t = std::function<void()>;const int threadnum = 5;class ThreadData
{
public:ThreadData(const std::string &name, const uint64_t &ctime, func_t f): threadname(name), createtime(ctime), func(f){}public:std::string threadname;uint64_t createtime;func_t func;
};void Print()
{std::cout << "我是線程執行的大任務的一部分" << std::endl;
}// 新線程
void *ThreadRountine(void *args)
{int a = 10;ThreadData *td = static_cast<ThreadData *>(args);while (true){std::cout << "new thread"<< " thread name: " << td->threadname << " create time: " << td->createtime << std::endl;td->func();if(td->threadname == "thread-4"){std::cout << td->threadname << " 觸發了異常!!!!!" << std::endl;// a /= 0; // 故意制作異常}sleep(1);}
}
// 如何給線程傳參,如何創建多線程呢??? -- done
// 研究兩個問題: 1. 線程的健壯性問題 2. 觀察一下thread id// 獲取返回值
// 主線程
int main()
{std::vector<pthread_t> pthreads;for (size_t i = 0; i < threadnum; i++){char threadname[64];snprintf(threadname, sizeof(threadname), "%s-%lu", "thread", i);pthread_t tid;ThreadData *td = new ThreadData(threadname, (uint64_t)time(nullptr), Print);pthread_create(&tid, nullptr, ThreadRountine, td);pthreads.push_back(tid);sleep(1);}std::cout << "thread id: ";for(const auto &tid: pthreads){std::cout << tid << ",";}std::cout << std::endl;while (true){std::cout << "main thread" << std::endl;sleep(3);}
}
????????解析:如上代碼我們按照順序進行解讀:創建了一個ThreadData類,用于存儲線程的名字、創建時間以及函數指針,接下來的Print()函數就是我們主要要傳遞的給ThreadData對象的函數,接下來的ThreadRountine函數則是用于傳遞給
pthread_create()
函數中的void *(*start_routine) (void *)函數指針變量,由于給線程執行。需要注意的是:其中有段代碼是故意制作除0錯誤的,用于驗證Linux使用多線程會造成健壯性降低的問題(只要其中一個線程出錯誤,那么其它線程也會收到影響,全部退出)。主函數中先是new出來ThreadData類型的對象,再將他通過pthread_create()
函數中的*arg參數傳遞給新創建的線程。接著新線程執行對應的指令,主線程執行對應的指令。
pthread_self()
????????使用pthread_self()
函數可以獲取當前線程的ID。下面是pthread_self()
函數的聲明和用法示例:
#include <pthread.h>// 獲取當前線程ID
pthread_t current_thread_id = pthread_self();
????????在上面的示例中,
pthread_self()
函數被調用時,會返回當前線程的ID,并將其存儲在current_thread_id
變量中。
??pthread_self()
函數通常用于多線程程序中,當需要獲取當前線程的ID以進行一些特定的操作時使用。例如,可以使用當前線程的ID來區分不同線程的行為,或者將其作為參數傳遞給其他函數或數據結構。????????需要注意的是:
pthread_self()
函數只能獲取當前線程的ID,不能用于獲取其他線程的ID。如果需要獲取其他線程的ID,可以使用pthread_equal()
函數進行比較,或者將線程ID作為參數傳遞給其他函數進行處理。
????????在了解了這個函數后我們通過打印與ps -aL指令中的LWP做對比:
#include <iostream>
#include <pthread.h>
#include <unistd.h>void *ThreadRountine(void *args)
{usleep(1000);int a = 10;std::string name = static_cast<const char *>(args);while (true){std::cout << "i am a new thread, my name:" << name << " my id:" << pthread_self() << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThreadRountine,(void*)("thread -1"));while(true){std::cout << "i am man thread," << " my id:" << pthread_self() << std::endl;sleep(1);}return 0;
}
????????我們發現左半邊為一串很大的數字,與右邊完全不同:
????????接著將左半邊的數字轉換為16進制看看?我們發現他同地址很像,他們之間難道有什么關聯嗎?是的,因為thread id的本質就是一個地址!
如何終止線程?
通過return nullptr來線程終止
????????如下:在對應傳入的函數中返回nullptr,需要特別注意:我們能使用exit()函數來退出嗎?答案是不能!因為exit()是“進程終止”!如果調用,整個進程都會退出!
void *ThreadRountine(void *args)
{usleep(1000);int a = 10;std::string name = static_cast<const char *>(args);while (a--){std::cout << "i am a new thread, my name:" << name << " my id:" << ToHex(pthread_self()) << std::endl;sleep(1);}return nullptr;
}
通過pthread_exit()來線程終止
? ? pthread_exit()
是POSIX線程庫中的一個函數,用于終止當前線程的執行。
????????下面是pthread_exit()
函數的聲明和用法示例:
#include <pthread.h>// 終止當前線程的執行
pthread_exit(nullptr);
????????在上面的示例中,
pthread_exit()
函數被調用時,會立即終止當前線程的執行,并返回到主線程或調用者。傳遞給pthread_exit()
函數的參數是一個指向返回值的指針,這個返回值可以被其他線程通過pthread_join()
函數獲取。如果不需要傳遞返回值,可以傳遞nullptr
作為參數。????????需要注意的是:
pthread_exit()
函數不會釋放線程所占用的資源,如堆棧、文件描述符等。這些資源的釋放需要程序員手動進行。
通過pthread_cancel()來取消線程(先看后面的等待在回頭看這里)
? ? pthread_cancel()
函數用于取消一個線程的執行。它的原型如下:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
參數說明:
thread
:需要取消執行的線程ID。返回值:
- 成功時返回0;失敗時返回錯誤碼。
????????使用
pthread_cancel()
函數可以強制終止一個線程的執行,但需要注意的是:該函數并不會釋放線程所占用的資源,如堆棧、線程描述符等。因此,在線程被取消后,還需要調用其他函數來回收這些資源。
????????下面是一個使用pthread_cancel()
函數的例子:
#include <iostream>
#include <pthread.h>
#include <unistd.h>void* thread_function(void* arg) {int i;for (i = 0; i < 10; i++) {std::cout << "Thread is running..." << std::endl;sleep(1);}return NULL;
}int main() {pthread_t thread;int result = pthread_create(&thread, NULL, thread_function, NULL);if (result != 0) {std::cerr << "Error creating thread!" << std::endl;return 1;}sleep(3); // 讓線程運行一段時間result = pthread_cancel(thread); // 取消線程執行if (result != 0) {std::cerr << "Error cancelling thread!" << std::endl;return 1;}// 等待線程結束,并回收資源void* retval;result = pthread_join(thread, &retval);if (result != 0) {std::cerr << "Error joining thread!" << std::endl;return 1;}std::cout << "Thread has been cancelled and joined successfully." << std::endl;return 0;
}
????????在這個例子中,我們創建了一個新線程,并在主線程中等待3秒鐘后調用pthread_cancel()函數來取消該線程的執行。然后,我們使用pthread_join()函數等待線程結束,并回收其資源。
????????需要注意的是:線程如果是被分離的,他是可以被取消的,但是不能被join。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
- 如果thread線程通過return返回,value_ ptr所指向的單元里存放的是thread線程函數的返回值。
- 如果thread線程被別的線程調用pthread cancel異常終掉,value ptr所指向的單元里存放的是常數
PTHREAD_ CANCELED。- 如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。
- 如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ ptr參數。
線程等待
????????我們都知道進程退出,他的PCB不會立即釋放,會處于僵尸狀態,進程要等待。那么線程也需要等待嗎?
????????是的,線程也是需要等待的!因為:線程退出沒有等待,會導致累充進程的僵尸問題。我們可以通過pthread_join()來等待線程!
pthread_join()
? ? pthread_join()
函數用于等待一個線程的結束,并回收其資源。它的原型如下:
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
參數說明:
thread
:需要等待的線程ID。retval
:指向一個指針的指針,用于存儲被等待線程的返回值。如果不關心返回值,可以設置為nullptr。(為什么是void ** 類型呢?因為我們通過pthread_create傳入線程的函數的返回值是void *類型的返回值,我們用void **就可以接收這個函數的返回值,retval是一個輸出型的參數)返回值:
- 成功時返回0;失敗時返回錯誤碼。
????????例子1:
#include <iostream>
#include <pthread.h>
#include <unistd.h>void* print_hello(void* arg) {std::cout << "Hello from thread!" << std::endl;sleep(2);return NULL;
}int main() {pthread_t thread;void* retval;int ret;// 創建線程ret = pthread_create(&thread, NULL, print_hello, retval);if (ret != 0) {std::cerr << "Error creating thread!" << std::endl;return 1;}// 等待線程結束ret = pthread_join(thread, nullptr);if (ret != 0) {std::cerr << "Error joining thread!" << std::endl;return 1;}std::cout << "Thread joined successfully!" << std::endl;return 0;
}
????????在這個例子中,我們創建了一個新線程,該線程執行print_hello函數。然后,我們使用pthread_join()函數等待線程結束。當線程結束時,pthread_join()函數返回0,表示成功。
????????例子2:
#include <iostream>
#include <pthread.h>
#include <unistd.h>std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}class ThreadReturn
{
public:ThreadReturn(pthread_t id, const std::string &info, int code): id_(id), info_(info), code_(code){}public:pthread_t id_;std::string info_;int code_;
};void *threadRoutine(void *arg)
{int cnt = 5;while (cnt--){std::cout << (const char *)arg << " is running..." << std::endl;sleep(1);}ThreadReturn *ret = new ThreadReturn(pthread_self(), "thread quit normal", 10);return ret;
}int main()
{// newpthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)("thread -1"));void *ret = nullptr;pthread_join(tid, &ret);ThreadReturn *r = static_cast<ThreadReturn *>(ret);std::cout << "main thread get new thread info:" << r->code_ << ", " << ToHex(r->id_) << ", " << r->info_ << std::endl;delete r;// mainwhile (true){std::cout << "i am man thread,"<< " my id:" << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}
????????本例返回的是一個ThreadReturn *的值,運行效果如下:
pthread_detach()
????????線程默認的模式是joinable的,也就是說線程退出必須得等待,主線程必須是阻塞的等待新線程的。但是,我們也是可以設置為分離狀態的!即:我們可以設置為非阻塞狀態的,線程在退出時,對應的資源會被直接被回收。
? ? pthread_detach()
函數用于將線程設置為分離狀態,從而在線程終止時自動回收其資源。它的原型如下:
#include <pthread.h>
int pthread_detach(pthread_t thread);
參數說明:
thread
:需要設置為分離狀態的線程ID。返回值:
- 成功時返回0;失敗時返回錯誤碼。
需要注意的是:可以線程自己分離也可以由主線程分離。
????????例子:
#include <iostream>
#include <pthread.h>void* thread_function(void* arg) {// 線程執行的代碼return nullptr;
}int main() {pthread_t thread;int result = pthread_create(&thread, nullptr, thread_function, nullptr);if (result != 0) {std::cerr << "Error creating thread!" << std::endl;return 1;}// 分離線程result = pthread_detach(thread);if (result != 0) {std::cerr << "Error detaching thread!" << std::endl;return 1;}// 主線程的其他操作return 0;
}
????????在這個例子中,我們首先創建了一個新線程,然后立即將其分離。這樣,當線程結束時,它的資源會被自動回收,而不需要主線程顯式等待其結束。
一個小拓展
????????我們都知道進程退出會有退出碼,那線程退出會有退出碼嗎?答案是沒有!因為如果線程因為異常終止了,那么整個進程也會跟著終止,所以不需要退出碼!
線程id詳解
pthread庫知識補充
????????前面我們提到的對于線程控制的接口,實際上都不是系統直接提供的接口,而是原生線程庫pthread(系統會自帶這個庫)提供的接口。圖示如下:
????????通過pthread庫,可以對線程進行管理,我們通過“先描述,在組織”的原則會在pthread庫里面實現對應的結構體對象來描述,再通過一定的數據結構來組織。里面會包涵系統中“輕量級進程”的信息也會包涵用戶的“用戶級線程”信息。
?
clone()
????????clone()函數是Linux中的一個系統調用,用于創建新的執行線程或進程。它是fork()系統調用的泛化形式,具有更高的靈活性。實際上進程與線程的創建都是它的封裝。以下是對clone()函數的詳細解析:
1、函數原型:
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *pid, struct user_desc *tls, pid_t *ctid */);
2、參數說明:
-
(*fn)(void *)
:子進程(或線程)執行時調用的函數。child_stack
:為子進程分配的堆棧指針。flags
:控制新進程與原進程之間的共享資源以及其它行為的標志位集合。arg
:傳給子進程的參數,一般為0。...
:可選的附加參數,包括pid_t *pid
,struct user_desc *tls
,pid_t *ctid
。
3、flags參數詳解:
-
CLONE_PARENT
:創建的子進程的父進程是調用者的父進程,使新進程與創建它的進程成為“兄弟”關系。CLONE_FS
:子進程與父進程共享相同的文件系統,包括root、當前目錄、umask等。
????????4、與fork()的區別:直接調用fork()等效于調用clone()時僅指定flags為SIGCHLD(共享信號句柄表)。fork()是Unix標準的復制進程的系統調用,而Linux實現了fork(), vfork(), clone三個系統調用。其中vfork()創造出來的是輕量級進程,也叫線程,是共享資源的進程。
????????5、使用場景:clone()通常用于實現多線程編程,因為它可以精細地控制哪些資源是共享的,哪些是私有的。這在多線程編程中是非常重要的,因為它允許創建高度定制的線程行為。
????????6、glibc封裝:從Linux 2.3.3開始,glibc的fork()封裝作為NPTL(Native POSIX Threads Library)線程實現的一部分。創建線程的函數pthread_create內部使用的也是clone函數。
系統調用問題
????????前面我們談到線程雖然有很多的共享資源,但是也要有獨立的屬性,其中最重要的是:1、上下文。2、棧。
????????其中棧是每個新線程會在pthread庫中維護的,而默認地址空間中的棧由主線程使用。前面在學習Linux動靜態庫的時候提到:加載庫會將庫加載到棧與堆之間的共享區中,pthread庫當然也是,而庫中的棧則是在其所屬進程的虛擬地址空間中分配。
如何理解pthread庫來管理線程
????????如下這張圖想必大家都已經很熟悉了,我們在磁盤上存儲的pthread庫以及使用到pthread庫的可執行程序都會被加載到物理內存中,然后通過頁表映射到地址空間上。動態庫,也叫共享庫,只要在物理內存中映射了一次,之后都會被其他進程所共享。因此,pthread庫會管理整個系統中所創建的進程!理解上:線程庫是共享的,所以,內部要管理整個系統,多個用戶所啟動的所有線程。
????????如下為較為詳細的pthread理解:其中mmap區域就是共享區,而其中動態庫中struct_pthread可以理解為“先描述”,也可以理解為“TCB”。線程棧可以理解為一個指針,指向對應棧的地址。struct_pthread、線程局部存儲和線程棧可以理解為一個一個的塊,每一個線程都有。與接下來的一個一個的塊被管理起來,可以理解為“在組織”。這些屬性都會被庫所維護。
????????如上struct_pthread會儲存對應的退出信息,而我們的pthread_join()函數接口就是讀取上面struct_pthread中的信息。如何找到的呢?我們是根據pthread_t tid來找到的,pthread_t tid就是線程屬性集合在庫中的地址!而其他pthread庫中的接口也就是根據這個就是對庫中的這些數據來進行維護的!
前面提到的LWP為什么和pthread_self()獲得的不同?
????????pthread_ create函數會產生一個線程ID,存放在第一個參數指向的地址中。該線程ID和前面說的線程ID不是一回事。
????????前面講的線程ID屬于進程調度的范疇。因為線程是輕量級進程,是操作系統調度器的最小單位,所以需要一個數值來唯一表示該線程。
????????pthread_ create函數第一個參數指向一個虛擬內存單元,該內存單元的地址即為新創建線程的線程ID,屬于NPTL線程庫的范疇。線程庫的后續操作,就是根據該線程ID來操作線程的。
?
線程局部存儲是啥?
__thread
? ? __thread
是GCC提供的線程局部存儲(Thread-Local Storage, TLS)的關鍵字。
? ? __thread
用于聲明線程局部變量,這意味著每個線程都會有該變量的一個獨立實例。不同線程中的__thread
變量互不干擾,各自保有自己獨立的值,這對于多線程編程中需要為每個線程保持獨立狀態的場景非常有用。
????????具體來說,__thread
關鍵字的使用場景和限制包括:
- 應用場景:適合修飾那些具有全局性質且值可能會發生變化的變量,但又不需要像全局變量那樣進行保護的情況。
- 效率優勢:
__thread
變量的存取效率可以與全局變量相媲美,這使得它在性能上非常有吸引力。- 使用限制:只能修飾POD(Plain Old Data)類型,即那些不含有自定義構造、拷貝、賦值、析構函數的簡單數據類型。因為
__thread
無法自動調用類的構造和析構函數,所以它不能用來修飾類類型的變量。- 作用范圍:可以用來修飾全局變量和函數內的靜態變量,但不能修飾函數的局部變量或類的普通成員變量。
- 初始化限制:
__thread
變量的值只能初始化為編譯器常量。
????????總的來說,在多線程編程中,__thread
提供了一種方便高效的方式來為每個線程創建獨立的變量副本,從而避免了共享數據帶來的競爭條件和同步問題。
線性局部存儲示例
????????如下為正常的全局變量在多線程情況下的示例:也印證了多線程共享資源的特性!
#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>using namespace std;int g_val = 100; // 全局變量,本身就是被所有線程共享的void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);sleep(1);while (true){sleep(1);std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;g_val++;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread1");while (true){sleep(1);std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;}pthread_join(tid, nullptr);return 0;
}
????????如下為使用__thread的示例:可以發現本來共享的全局變量變成了線性局部的變量,值和地址都會變化!其中拓展了對于獲取LWP通過調用系統調用 SYS_gettid 獲取當前線程的 TID。
#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>using namespace std;// int g_val = 100; // 全局變量,本身就是被所有線程共享的
__thread int g_val = 100; // 線程的局部存儲!有什么用?有什么坑?__thread pid_t lwp = 0;// __thread std::string threadname;pid_t gettid() {return syscall(SYS_gettid);
}void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);lwp = gettid(); // 調用系統調用 SYS_gettid 獲取當前線程的 TIDwhile (true){sleep(1);std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;std::cout <<"new thread: " << lwp << std::endl;g_val++;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread1");lwp = gettid(); // 調用系統調用 SYS_gettid 獲取當前線程的 TIDstd::cout <<"main thread: " << lwp << std::endl;while (true){sleep(1);std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;}pthread_join(tid, nullptr);
}
???????????????????????感謝你耐心的看到這里?( ′・?・` )比心,如有哪里有錯誤請踢一腳作者o(╥﹏╥)o!?
????????????????????????????????? ? ? ?
????????????????????????????????????????????????????????????????????????給個三連再走嘛~??