【Linux取經路】初識線程——線程控制

文章目錄

  • 一、什么是線程?
    • 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 虛擬地址到物理地址的轉換

image-20240311181055099

站在地址空間角度,線程分配資源本質就是分派地址空間范圍。

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_IGNSIG_DFL 或者自定義的信號處理函數)
  • 當前工作目錄
  • 用戶 id 和組 id

image-20240311215931222

二、線程控制

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;
}

image-20240312165319396

在編譯線程代碼的時候,需要加上 -lpthread 選項。因為 pthread.h 是第三方庫,但是 g++ 編譯器僅僅可以找到 pthread.h 和該庫的位置,并不會默認幫我們去鏈接 pthread 庫,因此我們要加 -lpthread 選項,告訴 g++ 編譯器,我們要鏈接這個庫。

image-20240312171343716

ps -aL:其中 L 表示查看當前操作系統中的所有輕量級線程。

image-20240312171751985

  • 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;
}

image-20240312222311854

2.3 pthread_join——線程等待

#include <pthread.h>int pthread_join(pthread_t thread, void **retval);
  • thread:要等待的線程 ID
  • retval:輸出型參數,獲取線程函數的返回值
  • **返回值:**等待成功0被返回;等待失敗,錯誤碼被返回

image-20240312230358310

線程等待的目的:

  • 防止新線程內存泄露

  • 主線程獲取子線程的執行結果

#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;
}

image-20240313083537658

線程執行完它的函數后就退出了,主線程在等待的時候,默認是阻塞等待。主線程等待子線程,只能獲取到子線程執行函數的返回值,不考慮子線程出異常,因為子線程一旦出異常,主線程也會跟著遭殃。

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;
}

image-20240313084734928

注意: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;
}

image-20240313085650274

一個進程被取消,它的返回值是 PTHREAD_CANCELED 一個宏:

image-20240313085905095

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;
}

image-20240313090718302

這說明代碼區對所有的線程來說是共享的。

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;
}

image-20240313095804127

該實例證明了堆空間也是共享的。

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;
}

image-20240313101642708

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;
}

image-20240313103852295

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 */ );

image-20240313105433864

線程的概念是 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;
}

image-20240313141716510

每個線程都去調用了 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 = &num; // 將線程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;
}

image-20240313143018887

雖然這樣做可以,但是我們在代碼中是禁止這樣做的。

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 = &num; // 將線程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;
}

image-20240313151304588

此時 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 = &num; // 將線程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;
}

image-20240313151716109

子線程各自只執行了一次,是因為,子線程在被創建出來之后,主線程立即將所有的子線程進行了分離,然后,主線程又去進行 join,此時因為所有的子線程已經被分離了,主線程去 join 就不會阻塞等待了,而是直接出錯返回,最后主線程執行完畢就直接退出了,主線程退出,也就意味著進程退出,進程退出所有的資源就要被釋放,所有子線程賴以生存的環境也沒了,所以子線程也就跟著沒了。因此,線程分離后,要保證主線程最后退出,常見的情況是主線程跑死循環,一直不退出。

線程分離本質上是線程 TCB 中的一個屬性,pthread_detach 本質上就是去修改改屬性。

三、結語

今天的分享到這里就結束啦!如果覺得文章還不錯的話,可以三連支持一下,春人的主頁還有很多有趣的文章,歡迎小伙伴們前去點評,您的支持就是春人前進的動力!

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/13517.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/13517.shtml
英文地址,請注明出處:http://en.pswp.cn/web/13517.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關于基礎的流量分析(1)

1.對于流量分析基本認識 1&#xff09;簡介&#xff1a;網絡流量分析是指捕捉網絡中流動的數據包&#xff0c;并通過查看包內部數據以及進行相關的協議、流量分析、統計等來發現網絡運行過程中出現的問題。 2&#xff09;在我們平時的考核和CTF比賽中&#xff0c;基本每次都有…

MySQL用戶管理操作

用戶權限管理操作 DCL語句 一.用戶管理操作 MySQL軟件內部完整的用戶格式&#xff1a; 用戶名客戶端地址 admin1.1.1.1這個用戶只能從1.1.1.1的客服端來連接服務器 admin1.1.1.2這個用戶只能從1.1.1.2的客服端來連接服務器 rootlocal host這個用戶只能從服務器本地進行連…

Prompt - 流行的10個框架

轉載自&#xff1a;https://juejin.cn/post/7287412759050289212 文章目錄 1、ICIO框架2、CRISPE框架3、BROKE框架4、CREATE框架5、TAG框架6、RTF框架7、ROSES框架8、APE框架9、RACE框架10、TRACE框架 測試用例 為了看到不同的Prompt框架效果&#xff0c;本文定義一個統一的測…

ACM實訓

【碎碎念】繼續搞習題學習&#xff0c;今天完成第四套的ABCD&#xff0c;為下一周擠出時間復習&#xff0c;加油 Digit Counting 問題 法希姆喜歡解決數學問題。但有時解決所有的數學問題對他來說是一個挑戰。所以有時候他會為了解決數學難題而生氣。他拿起一支粉筆&#xff…

Java面試八股之進程和線程的區別

Java進程和線程的區別 定義與作用&#xff1a; 進程&#xff1a;在操作系統中&#xff0c;進程是程序執行的一個實例&#xff0c;是資源分配的最小單位。每個進程都擁有獨立的內存空間&#xff0c;包括代碼段、數據段、堆空間和棧空間&#xff0c;以及操作系統分配的其他資源…

工廠模式(簡單工廠模式+工廠模式)

工廠模式的目的就是將對象的創建過程隱藏起來&#xff0c;從而達到很高的靈活性&#xff0c;工廠模式分為三類&#xff1a; 簡單工廠模式工廠方法模式抽象工廠模式 在沒有工廠模式的時候就是&#xff0c;客戶需要一輛馬車&#xff0c;需要客戶親自去創建一輛馬車&#xff0c;…

PDF之Blend Mode(混合模式)BM(對應OFD的BlendMode)

Blend Mode&#xff08;混合模式&#xff09;用于定義對象與背景或其他對象之間的顏色混合方式。PDF支持多種混合模式&#xff0c;常見的混合模式包括&#xff1a; Normal&#xff1a;正常混合模式&#xff0c;將對象顏色直接疊加在背景上。 Multiply&#xff1a;乘法混合模式…

經驗分享:C++ error:‘syscall’ was not declared in this scope

明明已經加了頭文件 #include <sys/syscall.h>#define gettid() syscall(__NR_gettid)但是依舊不能使用 syscall() 函數&#xff0c; 檢查源碼后&#xff1a; sys/syscall.h 內部表示&#xff0c;他封裝了 打開對應的 syscall.h 文件內部依舊沒有 syscall()函數的聲明…

使用docker+jenkins構建前端項目發布到nginx

1.準備環境 為了方便公司開發優化代碼&#xff0c;不需要反復地將項目包發送給運維部署&#xff0c;我們對開發環境的前端項目利用jenkinsCI/CD進行自動化部署 需要兩臺服務器 一臺jenkins 一臺發布服務器,這里發布服務器 我直接使用開發環境的服務器 將admin界面與云計算展示…

全棧實現發送驗證碼注冊賬號 全棧開發之路——全棧篇(3)

全棧開發一條龍——前端篇 第一篇&#xff1a;框架確定、ide設置與項目創建 第二篇&#xff1a;介紹項目文件意義、組件結構與導入以及setup的引入。 第三篇&#xff1a;setup語法&#xff0c;設置響應式數據。 第四篇&#xff1a;數據綁定、計算屬性和watch監視 第五篇 : 組件…

基于JAVA的Dubbo 實現的各種限流算法

在基于 Java 的 Dubbo 實現中&#xff0c;限流&#xff08;Rate Limiting&#xff09;同樣是一個關鍵的需求。Dubbo 是阿里巴巴開源的一款高性能 Java RPC 框架&#xff0c;廣泛應用于分布式服務架構中。實現限流可以幫助服務在高并發場景下保持穩定性和可靠性。以下是幾種常見…

Linux進程調度與切換、環境變量

文章目錄 Linux優先級Linux的調度與切換**進程切換**&#xff1a;**進程調度**&#xff1a;優先級活動隊列過期隊列active指針和expired指針 環境變量main函數參數 int main(int argc, char *argv[], char *envp[]) 環境變量環境變量和本地變量echo查看單個環境變量的方法expor…

藍牙模塊在無人機 ID識別、標準制定發揮的作用及其應用優勢和面臨的挑戰

隨著科技的飛速發展&#xff0c;無人機已經廣泛應用于航拍、農業、救援、物流等多個領域。而在無人機的通信與控制系統中&#xff0c;藍牙模塊扮演著重要的角色。本文將探討藍牙模塊在無人機Remote ID識別和標準制定執行中發揮的作用&#xff0c;并分析其應用優勢和面臨的挑戰。…

裝飾器模式在JS中的應用

裝飾器模式在JavaScript中的應用主要是通過修飾函數或類來添加額外的功能或行為。 在ES6中&#xff0c;裝飾器模式可以通過使用語法糖來實現。我們可以將裝飾器應用于函數、類、方法或屬性等。下面是一些在JavaScript中使用裝飾器模式的示例&#xff1a; 修飾函數&#xff1a…

2.Spring中用到的設計模式

Spring框架中使用了多種設計模式來構建其強大且靈活的功能&#xff0c;這里舉例說明Spring中的一些功能使用到的設計模式。 工廠模式&#xff1a;Spring容器本質是一個大工廠&#xff0c;使用工廠模式通過BeanFactory和ApplicationContext這兩個核心接口來創建和管理bean對象。…

Java讀取串口及端口調試

本篇主要講述使用Java對串口進行讀取和發送操作 準備 在項目中導入第三方Jar包 Jar包已經在資源中綁定&#xff0c;或者去官網上自行下載jSerialComm 注意當前jar包是配合JDK1.8環境使用&#xff0c;如果是1.8以下程序將直接中斷 安裝虛擬串口的軟件 Configure Virtual Seri…

一款功能強大的安卓虛擬機應用——VMOS Pro使用分享

前段時間我剛剛分享一個WeChat平板模塊能夠允許用戶自由修改系統設置&#xff0c;讓你的Android備用手機煥發新生&#xff0c;實現手機PAD化&#xff0c;實現兩臺設備同時登錄微信號。今天我分享的這個相比WeChat更為簡單&#xff0c;因為它可以通過虛擬機的方式進行多種androi…

分類和品牌關聯

文章目錄 1.數據庫表設計1.多表關聯設計2.創建表 2.使用renren-generator生成CRUD1.基本配置檢查1.generator.properties2.application.yml 2.生成代碼1.進入localhost:81生成代碼2.將main目錄覆蓋sunliving-commodity模塊的main目錄 3.代碼檢查1.注釋掉CategoryBrandRelationC…

Tencent : TBDS簡介

Tencent TBDS&#xff08;Tencent Big Data Suite&#xff09;是騰訊公司推出的大數據處理套件&#xff0c;它基于騰訊多年海量數據處理經驗&#xff0c;依托云原生技術和泛Hadoop生態開源技術&#xff0c;為用戶提供可靠、安全、易用的大數據處理平臺。 TBDS可以在公有云、私…

JavaWeb基礎(HTML,CSS,JS)

這些知識用了三四天左右學完&#xff0c;因為是JavaWeb&#xff0c;并不是前端&#xff0c;所以只是夠用&#xff0c;不是深入&#xff0c;但是這確實是學校一個學期交的東西&#xff08;JavaWeb課程&#xff09;。 總結一下網頁分為三部分&#xff1a;HTML(內容結構),CSS&…