@TOC
📝進程和線程
- 進程是資源分配的基本單位
- 線程是調度的基本單位
- 線程共享進程數據,但也擁有⾃⼰的⼀部分數據:
- 線程ID
- 一組寄存器
- 棧
- errno
- 信號屏蔽字
- 調度優先級
🌠 進程的多個線程共享
同⼀地址空間,因此TextSegment、DataSegment都是共享的,如果定義⼀個函數,在各線程中都可以調⽤,如果定義⼀個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:
- 文件描述符表
- 每種信號的處理方式(SIG_IGN、SIG_ DFL或者自定義的信號處理函數)
- 當前工作目錄
- 用户id和組id
進程和線程的關係如下圖:
🌉關於進程線程的問題
linux如何看待之前學習的單進程?具有⼀個線程執⾏流的進程
在Linux中,單進程是資源分配基本單位,有獨立內存與CPU時間片,由PCB管理。其指令順序執行,阻塞操作會致進程暫停。單進程難以利用多核並行,實現併發受限。它獨立性高,崩潰不影響其他進程,但容錯性差,邏輯簡單利於調試維護。
🌠Linux線程控制
🌉POSIX線程庫
- 與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以“pthread_”打頭的
- 要使用這些函數庫,要通過引入頭文<pthread .h>
- 鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項
🌠創建線程
功能:創建一個新的線程原型:
int pthr/ead_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void*), void *arg);
參數:
thread:返回線程ID
attr:設置線程的屬性,attr為NULL表示使用默認屬性start_routine:是個函數地址,線程啓動後要執行的函數arg:傳給線程啓動函數的參數
返回值:成功返回0;失敗返回錯誤碼
錯誤檢查:
- 傳統的⼀些函數是,成功返回0,失敗返回-1,並且對全局變量errno賦值以指⽰錯誤。pthreads函數出錯時不會設置全局變量errno(⽽⼤部分其他POSIX函數會這樣做)。⽽是將錯誤代碼通過返回值返回
- pthreads同樣也提供了線程內的errno變量,以⽀持其它使⽤errno的代碼。對於pthreads函數的錯誤,建議通過返回值業判定,因為讀取返回值要⽐讀取線程內的errno變量的開銷更⼩
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
void *rout(void *arg)
{
int i;
for (;;)
{
printf("I'am thread 1\n");
sleep(1);
}
}
int main(void)
{
pthread_t tid;
int ret;
if ((ret = pthread_create(&tid, NULL, rout, NULL)) != 0)
{
fprintf(stderr, "pthread_create : %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
int i;
for (;;)
{
printf("I'am main thread\n");
sleep(1);
}
}
#include <pthread .h>
//獲取線程ID
pthread_t pthread_self(void);
打印出來的tid是通過pthread庫中有函數pthread_self 得到的,它返回一個pthread_t類型的變量,指代的是調用pthread_self 函數的線程的“ID”。
怎麼理解這個“ID”呢?這個“ID”是pthread庫給每個線程定義的進程內唯一標識,是pthread庫維持的。
由於每個進程有自己獨立的內存空間,故此“ID”的作用域是進程級而非系統級(內核不認識)。
其實pthread庫也是通過內核提供的系統調用(例如clone)來創建線程的,而內核會為每個線程創建系統全局唯一的“ID”來唯一標識這個線程。
使⽤PS命令查看線程信息 運⾏代碼後執⾏:
$ ps -aL | head -1 && ps -aL \ grep mythreadPID LWP TTY
T工ME CMD
2711838 2711838 pts/ 23500:00:00 mythread
2711838 2711839 pts/23500:00:00 mythread
-L選項:打印線程信息
LWP是什麼呢?LWP得到的是真正的線程ID。之前使用pthread_self得到的這個數實際上是一個地址,在虛擬地址空間上的一個地址,通過這個地址,可以找到關於這個線程的基本信息,包括線程ID,線程棧,寄存器等屬性。
在ps -aL 得到的線程ID,有一個線程ID和進程ID相同,這個線程就是主線程,主線程的棧在虛擬地址空間的棧上,而其他線程的棧在是在共享區(堆棧之間),因為pthread系列函數都是pthread庫提供給我們的。而pthread庫是在共享區的。所以除了主線程之外的其他線程的棧都在共享區。
🌉線程終⽌
如果需要只終⽌某個線程⽽不終⽌整個進程,可以有三種⽅法:
- 從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit
- .線程可以調用pthread_ exit終止自己。
- 一個線程可以調用pthread_ cancel終止同一進程中的另一個線程。
pthread_exit函數
功能:線程終⽌
原型:
void pthread_exit(void *value_ptr);
參數:
value_ptr:value_ptr
不要指向⼀個局部變量。
返回值:
⽆返回值,跟進程⼀樣,線程結束的時候⽆法返回到它的調⽤者(⾃⾝)
需要注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是⽤malloc分配的,不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。
pthread_cancel函數
功能:取消⼀個執⾏中的線程
原型:
int pthread_cancel(pthread_t thread);
參數:
thread:線程ID
返回值:成功返回 0 ;失敗返回錯誤碼
🌉線程等待
為什麼需要線程等待?
- 已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內。
- 創建新的線程不會復⽤剛才退出線程的地址空間。
功能:等待線程結束
原型
lint pthread_join(pthread_t thread, void **value_ptr);
參數:
thread :線程ID
value_ptr:它指向一個指針,後者指向線程的返回值
返回值:成功返回0;失敗返回錯誤碼
調⽤該函數的線程將掛起等待,直到id為thread的線程終⽌。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參數。
#include <stdio.h>
#include <stdlib.h>
void *thread1(void* args)
{
printf("thread 1 returning ... \n");
int *p = (int*)malloc(sizeof(int));
*p = 1;
return (void*)p;
}
void* thread2(void *args)
{
printf("thread 2 exiting .. \n");
int *p = (int*)malloc(sizeof(int));
*p = 2;
pthread_exit((void*)p);
}
void* thread3(void* args)
{
while(1)
{
printf("thread 3 is running ... \n");
sleep(1);
}
return nullptr;
}
int main(void)
{
pthread_t tid;
void *ret;
//thread 1 return
pthread_create(&tid, nullptr, thread1, nullptr);
pthread_join(tid, &ret);
printf("thread return, thread id 0x%lX, return code:%d\n", tid, *(int*)ret);
free(ret);
// thread 2 exit
pthread_create(&tid, nullptr, thread2, nullptr);
pthread_join(tid, &ret);
printf("thread return , thread id 0x%lX, return code:%d\n", tid, *(int*)ret);
free(ret);
// thread 3 cancel by other
pthread_create(&tid, nullptr, thread3, nullptr);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
if( ret == PTHREAD_CANCELED)
printf("thread return ,thread id: 0x%lX, return code:PTHREAD_CANCELD\n", tid);
else
printf("thread return , thread id :0x%lX, return code: nullptr", tid);
}
🌉分離線程
默認情況下,新創建的線程是joinable的,線程退出後,需要對其進⾏pthread_join操作,否則⽆法釋放資源,從⽽造成系統泄漏。 如果不關⼼線程的返回值,join是⼀種負擔,這個時候,我們可以告訴系統,當線程退出時,⾃動釋放線程資源。
int pthread_detach(pthread_t thread);
可以是線程組內其他線程對⽬標線程進⾏分離,也可以是線程⾃⼰分離:
pthread_detach(pthread_self());
joinable和分離是衝突的,⼀個線程不能既是joinable⼜是分離的。
void* thread_run(void* args)
{
pthread_detach(pthread_self());
printf("%s\n", (char*)args);
return nullptr;
}
int main()
{
pthread_t tid;
if(pthread_create(&tid, nullptr, thread_run, (void*)"thread1 run ...") != 0)
{
printf("create thread error\n");
return 1;
}
int ret = 0;
sleep(1); //很重要, 要讓線程先分離, 再等待
if(pthread_join(tid, nullptr) == 0)
{
printf("pthread wait success\n");
ret = 0;
}
else
{
printf("pthread wait failed\n");
ret =1;
}
return ret;
}
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <thread>
//線程的局部存儲
int shared_value = 100;
std::string toHex(pthread_t tid)
{
//4,進程內的函數,線程共享
char buffer[64];
snprintf(buffer, sizeof(buffer), "0x%lx", tid);
return buffer;
}
void* start(void* args)
{
std::string name = static_cast<const char*>(args);
sleep(1);
while(true)
{
printf("I am a new thread , name: %s, shared_value: %d , &share_value: %p\n", name.c_str(), shared_value, &shared_value );
sleep(1);
}
return nullptr;
}
int main()
{
pthread_attr_t attr;
pthread_t tid;
pthread_create(&tid, nullptr, start, (void*)"thread-1");
std::cout<<"I am a new thread, name: main, "<< toHex(pthread_self())
<<", NEW thread id: " << toHex(tid) << std::endl;
while(true)
{
printf("main thread ,shared_value: %d, &shared_value: %p\n", shared_value, &shared_value);
shared_value += 10;
sleep(1);
}
pthread_join(tid, nullptr);
return 0;
}
while true; do ps -aL | head -1; ps -aL | grep mythread; sleep 1; done
//線程的局部存儲
__thread int shared_value = 100;
oid *start1(void *args)
{
std::string name = static_cast<const char *>(args);
int a = 100;
while (true)
{
std::cout << name << " local val a: " << a << std::endl;
a += 100;
if(1000 == a)
{
break;
}
sleep(1);
}
return nullptr;
}
int main()
{
// pthread_t tid1, tid2;
pthread_t tid;
pthread_create(&tid, nullptr, start1, (void *)"thread-1");
//pthread_detach(tid);
sleep(5);
void *ret = nullptr;
int n = pthread_join(tid, &ret); //
std::cout << " new thread exit code: " << (long long int)ret << " , n = " << n << std::endl;
return 0;
}
🌉創建多線程處理類
#include <queue>
class ThreadData
{
public:
ThreadData()
{
}
void Init(const std::string &name, int a, int b)
{
_name = name;
_a = a;
_b = b;
}
void Excute()
{
_result = _a + _b;
}
int Result() { return _result; }
std::string Name() { return _name; }
void SetId(pthread_t tid) { _tid = tid; }
pthread_t Id() { return _tid; }
int A() { return _a; }
int B() { return _b; }
~ThreadData()
{
}
private:
std::string _name;
int _a;
int _b;
int _result;
pthread_t _tid;
};
class outData
{
};
// 5全局變量在線程內部是共享的
int gval = 100;
std::queue<int> q;
char buffer[4096];
#define NUM 10
std::string toHex(pthread_t tid)
{
//進程內的函數, 線程共享
char tranbuffer[64];
snprintf(tranbuffer, sizeof(buffer), "0x%lx", tid);
return tranbuffer;
}
void *routine(void *args)
{
// std::string name = static_cast<const char *>(args);
ThreadData *td = static_cast<ThreadData *>(args);
while (true)
{
// 3,不加保護的情況下, 顯示器文件就是共享資源!
std::cout << "我是新線程, 我的名字是: " << td->Name()
<< ", my tid is : " << toHex(pthread_self())
<< ", 全局變量(會修改) : " << gval << std::endl;
gval++;
td->Excute();
sleep(1);
break;
}
//8,返回值問題:返回參數, 可以是變量, 數字, 對象!
// 8.1 理論上, 堆空間也是共享的! 誰拿着堆空間的入口地址,誰就能訪問該堆區
// int* p = new int(10);
// return (void*)p;
return td;
}
int main()
{
ThreadData td[NUM];
// 準備我們要加工處理的數據
for (int i = 0; i < NUM; i++)
{
char id[64];
snprintf(id, sizeof(id), "thread-%d", i);
td[i].Init(id, i * 10, i * 20);
}
// 創建多線程
for (int i = 0; i < NUM; i++)
{
pthread_t id;
pthread_create(&id, nullptr, routine, &td[i]);
td[i].SetId(id);
}
//等待多個線程
for(int i = 0; i < NUM; i++)
{
pthread_join(td[i].Id(), nullptr);
}
//彙總處理結果
for(int i = 0; i < NUM; i++)
{
printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].Id());
}
// 1,新線程和main線程誰先運行,不確定
// 2,線程創建出來, 要對進程的時間片進行瓜分
// 8,傳參問題: 傳遞參數,可以是變量,數字,對象!
// pthread_t tid1;
// ThreadData *td = new ThreadData("thread-1", 10, 20);
// pthread_create(&tid1, nullptr, routine, td);
return 0;
}
void* routine2(void* args)
{
std::string name = static_cast<const char* >(args);
while(true)
{
//3,在不加保護的情況下,顯示器文件就是共享資源!
std::cout<< "我是新線程,我的名字是:" << name << ", my tid is : "<< toHex(pthread_self()) << ", 全局變量(只是檢測):" << gval <<std::endl;
sleep(1);
// 6, 線程一旦出現異常, 可能會導致其他線程全部崩潰
// 6.1信號
int* p = nullptr;
*p = 100;
}
// return 0;
}
int main()
{
//
pthread_t tid1;
ThreadData *td = new ThreadData("thread-1", 10, 20);
pthread_create(&tid1, nullptr, routine2, td);
// 7,線程創建後也是 要等待和被回收的!
// 7.1 理由: a。類似殭屍進程的問題, b, 為了知道新線程的執行結果
ThreadData *rtd = nullptr;
int n = pthread_join(tid1, (void**)&rtd);//我們可以保證,執行完畢,任務一定處理完了,結果變量一定已經被寫入了!
if(n != 0 )
{
std::cerr << "join error: " << n << ", " << strerror(n) << std::endl;
return 1;
}
std::cout<< "join suceess!, ret: " << rtd->Result() << std::endl;
delete td;
}
🌠lamda表達式構造對象
lamda表達式構造對象也可以替換使用處理新線程方法
void* routine(void * args)
{
std::string name = static_cast<const char *>(args);
while(true)
{
std::cout<< "我是新線程 , 我的名字是: " << std::endl;
sleep(1);
}
return 0;
}
int main()
{
pthread_t tid ;
int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");
if(n != 0)
{
std::cout << "create thread error: " <<strerror(n) <<std::endl;
return 1;
}
while(true)
{
std::cout << "我是main線程..." <<std::endl;
sleep(1);
}
return 0;
}
while true; do ps -aL | head -1 && ps -aL | grep mythread ; sleep 1; done;
int main()
{
std::thread t([] () {
while(true)
{
std::cout<< "我是新線程, 我的名字 :new thread " << std::endl;
sleep(1);
}
});
while(true)
{
std::cout<< "我是main線程..." << std::endl;
sleep(1);
}
return 0;
}