初識Linux · 線程控制(1)

前言:

在前一篇文章中,我們已經探討了線程的基本概念,了解到可以通過ps -aL命令查看線程。由于線程的特殊性,我們需要在用戶層和操作系統層之間添加一個線程庫,并在編譯程序時將程序鏈接到這個線程庫。

我們也初步學習了如何創建線程,使用pthread_create函數。然而,僅通過創建和概念來學習線程是遠遠不夠的。因此,本文將詳細介紹線程的各種操作。

我們將從多個角度探討問題,然后嘗試模擬實現一個線程。


我們將從多個子問題出發來全面了解線程操作。

子問題一:主線程和新線程之間誰先運行?讓我們看一段代碼:

void* threadRun(void* args){     std::string name = static_cast<const char*>(args);     std::cout << name << " is running" << std::endl;     return nullptr; } <p>int main(){ pthread_t tid; pthread_create(&tid, nullptr, threadRun, (void<em>)"thread -1"); std::cout << "main thread is running" << std::endl; return 0; }

我們期望看到屏幕上打印兩條語句,以便討論誰先運行的問題。然而,實際運行多次后,卻沒有看到子線程的結果。

初識Linux · 線程控制(1)

這是因為我們沒有調用join函數。主線程在創建子線程后,不會等待子線程完成任務就直接退出。如果運氣好,子線程比主線程先完成任務,我們就能看到子線程的輸出。

通過這個問題,我們可以清楚地看到,主線程和子線程的運行順序是隨機的,這與進程的父子進程調度順序類似。

子問題二:我們期望誰是最后退出的呢?為什么

int main(){ pthread_t tid; void</em> result; pthread_create(&tid, nullptr, threadRun, (void<em>)"thread -1"); std::cout << "main thread is running" << std::endl; pthread_join(tid, &result); return 0; }

對于這個問題,基于進程的基礎知識,我們可以推測期望主線程最后退出。就像父進程需要收集子進程的退出信息一樣,子線程退出時,其信息也需要傳遞給主線程。我們如何查看子線程的信息呢?

在前一篇文章中,我們提到pthread_create的第一個參數是輸出參數,第二個參數是線程屬性,我們暫時將其設為nullptr。第三個參數是函數指針,返回值和參數都是void。我們可以直接使用這個參數,因為它來自pthread_create的第四個參數。

當我們以16進制打印tid時,會發現它是一個地址。如果主線程不等待子線程,可能會出現類似于僵尸進程的問題。因此,我們期望主線程是最后退出的線程。

子問題三:tid是什么?

std::string ToHex(pthread_t& tid){ char buffer[128]; snprintf(buffer, sizeof(buffer), "0x%lx", tid); return buffer; }

通過查看pthread_t的源碼,我們發現它實際上是一個無符號的長整型。我們通過該函數將其以16進制形式打印出來:

初識Linux · 線程控制(1)

它是一個地址,理解到這里就足夠了,關于它的更深層次內容,我們將在后續討論。

子問題四:全面看待線程函數的傳參,我們到底能傳些什么?

class ThreadData{ public: std::string _name; int _id; };</p><p>void<em> threadRunClass(void</em> args){ ThreadData t = <em>(ThreadData</em>)args; std::cout << t._name << " " << t._id << " is running" << std::endl; return nullptr; }</p><p>int main(){ pthread_t tid1; ThreadData t1; t1._name = "thread -1"; t1._id = 1; pthread_create(&tid1, nullptr, threadRunClass, (void<em>)&t1); pthread_join(tid1, nullptr); return 0; }

對于最后一個參數,我們可以傳遞字符串,那么我們能傳遞自定義的類成員嗎?

當然可以。如果你問為什么,我只能說:當然可以啊。

所以我們可以實現這樣的效果:

初識Linux · 線程控制(1)

然而,如果我們在主線程的區修改t1,可能會導致問題。因為線程有自己的棧,如果我們傳遞的是局部變量的地址,所有線程都可以看到這個地址,如果其中一個線程修改了它,就會引起問題。

因此,最安全的方式是在上分配空間,然后傳遞指針。

int main(){ pthread_t tid1; ThreadData t1; t1._name = "thread -1"; t1._id = 1; ThreadData </em>td = new ThreadData(); td->_name = "thread-2"; pthread_create(&tid1, nullptr, threadRunClass, (void*)&t1); pthread_create(&tid1, nullptr, threadRunClass, td); pthread_join(tid1, nullptr); return 0; }

對于這個問題,我們不能局限于傳遞內置類型,我們可以傳遞任何類型。

子問題五:全面看待線程的返回值。對于線程返回值的問題,類似于線程函數的傳參,我們可以返回不僅僅是nullptr,而是任意類型,只需進行強制類型轉換即可。

同學,到現在,你理解c語言c++泛型指針了嗎?

class ThreadResult{ public: bool _result; };</p><p>class ThreadData{ public: std::string _name; };</p><p>void<em> threadRun(void</em> args){ ThreadData<em> t = static_cast<ThreadData</em>>(args); std::cout << t->_name << " is running" << std::endl; ThreadResult<em> s = new ThreadResult(); s->_result = true; return (void</em>)s; }</p><p>int main(){ pthread_t tid; ThreadResult<em> result = nullptr; ThreadData</em> t = new ThreadData(); t->_name = "thread -1"; pthread_create(&tid, nullptr, threadRun, (void*)t); pthread_join(tid, (void**)&result); std::cout << "Thread result: " << result->_result << std::endl; delete t; delete result; return 0; }

初識Linux · 線程控制(1)

這段代碼是否還需要進一步解釋呢?

? 版權聲明
THE END
喜歡就支持一下吧
點贊7 分享