在linux內核中并沒有線程的概念,只有輕量級進程LWP的概念,linux下的線程都是是由LWP進行模擬實現的。因此linux操作系統中不會提供線程的相關接口,只會提供輕量級線程的接口(如vfork,clone等)。但是在我們的學習過程中實際上學到的都是線程的概念,故而linux的設計者在用戶和操作系統之間將輕量級進程的的系統調用進行了封裝成庫并提供給了用戶,這樣的線程我們稱之為用戶級線程庫。
接下來我們介紹一下linux系統中線程庫的使用與封裝。
一、線程庫的使用
1.1 POSIX線程庫
在linux中,與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以pthread_
打頭的。要想使用這些函數庫,要通過引入頭文件 <pthread.h>
,而在鏈接這些線程函數庫時要使用編譯器命令的-lpthread
選項。
1.2 線程庫的接口
1.2.1 線程創建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void* (*start_routine)(void*), void *arg);
//功能:創建一個新的線程
//參數://thread:輸出型參數,用以返回線程ID//attr:設置線程的屬性,attr為NULL表示使?默認屬性//start_routine:是個函數地址,即線程啟動后要執?的函數,它的返回值也可以是任意類型(整型,字符型,對象)//arg:傳給線程啟動函數的參數,可以是任意類型(整型,字符型,對象……)
//返回值:成功返回0,失敗返回錯誤碼
示例:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;void* run(void* arg)
{while(true){cout<<"new thread :"<<(char* )arg<<endl;sleep(1);}
}int main()
{pthread_t thread1;pthread_create(&thread1,nullptr,run,(void*)"new thread1");pthread_t thread2;pthread_create(&thread2,nullptr,run,(void*)"new thread2");while(true){cout<<"main thread"<<endl;sleep(1);}return 0;
}
注意:
? 新線程和主線程的運行順序是不確定的。
? 多線程的調度時間是基于對進程的時間片進行瓜分。
? 多個線程向同一個文件(這里是顯示器)進行寫入時,在不加保護的情況下是會發生重入的,也就會產生數據不一致問題。
? 線程是共享進程地址空間的。
? 線程出現異常會導致當前進程的其他線程全部崩潰。
1.2.2 線程終止
和進程一樣,線程創建之后也是要被等待和回收的!
如果需要只終止某個線程而不終止整個進程的話,有三種方法:
? 線程執行函數return。這種方法對主線程不適用,main函數return相當于調用exit。
? 線程可以調用pthread_exit終止自己。
void pthread_exit(void *value_ptr);
//功能:線程終?
//參數://value_ptr:線程退出時的返回值。
需要注意pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配。因為當其它線程得到這個返回指針時線程函數已經退出了。
? 一個線程可以調用pthread_cancel終止同一進程中的另一個線程。
int pthread_cancel(pthread_t thread);
//功能:取消一個執?中的線程
//參數://thread:要取消的線程ID
//返回值:成功返回0,失敗返回錯誤碼
1.2.3 線程等待
為什么需要線程等待?
因為已經退出的線程其空間沒有被釋放,仍然在進程的地址空間內。而創建新的線程不會復用剛才退出線程的地址空間。
int 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((void*)-1)。
? 如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。
? 如果對thread線程的終止狀態不感興趣,則可以傳NULL給value_ptr參數。
示例:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;void* run1(void* arg)
{int count=5;while(count--){cout<<"new thread :"<<(char* )arg<<endl;sleep(1);}pthread_exit((void*)100);cout<<"退出成功!"<<endl;}void* run2(void* arg)
{while(true){cout<<"new thread :"<<(char* )arg<<endl;sleep(1);}
}
int main()
{//創建線程1,線程2pthread_t thread1;pthread_create(&thread1,nullptr,run1,(void*)"new thread1");pthread_t thread2;pthread_create(&thread2,nullptr,run2,(void*)"new thread2");sleep(3);//取消線程2pthread_cancel(thread2);cout<<"取消成功!"<<endl;//線程等待void *ret1,*ret2;pthread_join(thread1,&ret1);pthread_join(thread2,&ret2);cout<<"等待成功!"<<endl;cout<<"ret1 :"<<(long long)ret1<<";ret2 :"<<(long long)ret2<<endl;return 0;
}
caryon@VM-24-10-ubuntu:~/linux/thread$ ./thread
new thread :new thread1
new thread :new thread2
new thread :new thread1
new thread :new thread2
new thread :new thread1
new thread :new thread2
取消成功!
new thread :new thread1
new thread :new thread1
等待成功!
ret1 :100;ret2 :-1
在多執行流的情況下,主執行流往往是最后退出的!
1.2.4 線程分離
默認情況下,新創建的線程是joinable的,線程退出后需要對其進型pthread_join操作,否則無法釋放資源從而造成系統泄漏。如果不關心線程的返回值的話join就是一種負擔。這個時候我們可以告訴系統,當線程退出時,自動釋放線程資源。
int pthread_detach(pthread_t thread);
//功能:進行線程分離
//參數://thread:被分離的線程ID
//返回值:成功返回0;失敗返回錯誤碼
可以是線程組內其他線程對目標線程進型分離,也可以是線程自己分離。joinable和分離是沖突的,一個線程不能既是joinable又是分離的。
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *thread_run(void *arg)
{printf("%s\n", (char *)arg);return NULL;
}
int main(void)
{//創建線程pthread_t tid;pthread_create(&tid, NULL, thread_run, (void*)"thread1 run...") ;//分離線程pthread_detach(tid);int ret = 0;sleep(1); if (pthread_join(tid, NULL) == 0){printf("pthread wait success\n");ret = 0;}else{printf("pthread wait failed\n");ret = 1;}return ret;
}
caryon@VM-24-10-ubuntu:~/linux/thread$ ./thread
thread1 run...
pthread wait failed
二、線程id與進程空間布局
在線程創建、終止、等待時我們都有注意到pthread_t thread(線程id)這個參數的存在,這個id表示的是什么意思呢?
#include<iostream>
#include<pthread.h>
using namespace std;void* run(void* args)
{while(true);return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,run,(void*)"newthread");printf("0x%lx\n",tid);return 0;
}
caryon@VM-24-10-ubuntu:~/linux/thread$ ./thread
0x7fae58e006c0
我們可以看到這個id的值是一個地址,那這個地址是什么地址呀?
通過ldd查看我們所形成的可執行程序是依賴pthread庫的,這個庫在程序運行時會加載到內存中,進而映射到進程地址空間里被所有的線程所共享。我們又知道linux系統中只有輕量級進程,線程是使用輕量級進程模擬實現的。但是用戶想要查看當前線程的屬性怎么辦呢?
linux內核中是存在這些屬性的,但是Linux中不存在線程呀!這需要內核層與用戶層進行解耦啊!故而線程的相關屬性也是由pthread庫進行維護的,用戶可以通過pthread庫來查看線程的屬性。
所以這個地址就是該線程的屬性所對應的進程地址空間中的地址!
我們可以使用下面這個函數獲取當前線程的id:
pthread_t pthread_self(void);
上圖就是進程地址空間中的tcb了。
? struct pthread
這個結構里面一定封裝了LWP。
? 線程棧
進程地址空間中的棧是主線程的棧,新線程的棧是通過動態申請創建的。
? 線程局部存儲
理論上來說全部變量是所有線程所共享的,即他們所訪問的全局變量的地址是相同的!但是在聲明全局變量是加上__thread
進行修飾(修飾的只能是內置類型)就表示給每個線程都來一份,即線程的局部性存儲。
#include<iostream>
#include<pthread.h>
using namespace std;__thread int count=100;void* run(void* args)
{printf("new thread &count : %p\n",&count);return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,run,(void*)"newthread");pthread_join(tid,nullptr);printf("main thread &count : %p\n",&count);return 0;
}
caryon@VM-24-10-ubuntu:~/linux/thread$ ./thread
new thread &count : 0x71e23b2006bc
main thread &count : 0x71e23ba8e4fc
三、線程的封裝
#pragma once#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>using func_t = std::function<void()>;
static int number = 1;
enum class TSTATUS
{NEW,RUNNING,STOP
};class Thread
{
private:// 成員方法!static void *Routine(void *args){Thread *t = static_cast<Thread *>(args);t->_status = TSTATUS::RUNNING;t->_func();return nullptr;}void EnableDetach() { _joinable = false; }bool IsJoinable() { return _joinable; }
public:Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true){_name = "Thread-" + std::to_string(number++);_pid = getpid();}bool Start(){if (_status != TSTATUS::RUNNING){int n = ::pthread_create(&_tid, nullptr, Routine, this); if (n != 0)return false;return true;}return false;}bool Stop(){if (_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Join(){if (_joinable){int n = ::pthread_join(_tid, nullptr);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}void Detach(){EnableDetach();pthread_detach(_tid);}~Thread(){}
private:std::string _name;pthread_t _tid;pid_t _pid;bool _joinable; // 是否是分離的,默認不是func_t _func;TSTATUS _status;
};