線程
前面講到進程:為了并發執行任務(程序),現代操作系統才引進進程
的概念
分析:
-
創建開銷問題:創建一個進程開銷:大
- 子進程需要拷貝父進程的整個地址空間
-
通信開銷問題:進程間的通信
- 需要用第三方(如:內核)
-
P1 -> copy -> 內核 -> copy -> P2
-
進程間通信代價或者開銷也是很大的。進程的地址空間是獨立,要通信的話需要用第三方的空
-
間。
于是,就有人提出能不能在 同一個(同一個進程內部)進程地址空間中進行任務的并發:線程/輕量級
進程
線程是一個比進程更小的活動單位。它是進程中的執行路徑(執行分支),線程也是并發的一種形式。
進程內部可以存在多個線程,它并發執行,但是進程內部的所有的線程共享整個進程的地址空間
main
函數:進程的主線程
1 線程特點
-
創建一個線程要比創建進程開銷要小很多
- 因為創建一個線程的話,不需要拷貝進程的地址空間
-
實現線程間的通信會更加方便
- 因為進程內部所有線程共享整個進程地址空間
-
線程也是一個動態概念:
- 線程(進程)狀態圖
- 就緒態:
ready
- 運行態:
running
- 阻塞態:
blocking
- 就緒態:
- 線程(進程)狀態圖
有了線程的概念之后
- 系統的調度單位就從進程變為線程,資源的分配還是以進程為單位
線程是進程內部的一個指令的執行分支,多個線程就是多個指令序列的并發執行。這些指令必須在函數
內部,線程的指令部分肯定是封裝一個函數的內部的。這個函數,就稱之為:線程函數,一個線程在創
建之后,要執行的指令全部封裝在該函數內部,這個線程函數執行完畢之后,該線程的任務也就執行完
了
2 線程函數的原型
typedef void *(*start_routine_t)(void *);// 函數指針:指向一個返回值為void*且帶有一個void*參數的函數
// 類型重定義:將一個函數指針類型重命名為start_routine_tvoid *my_thread(void *) // 咱自定義的線程函數:必須要符合返回值是void*且帶有void*參數的一
//個函數
{// 執行線程需要執行的代碼
}
3.Linux
對線程的API
的支持
Linux下采用的是POSIX Thread
線程庫。簡稱為:pthread
示例
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;string str = "你好,線程";
// 線程函數,表示線程啟動之后第一時間會執行的函數
void *mythread(void *arg)
{cout << "子線程:" << str << endl;return nullptr;
}int main()
{// 創建線程// 第一次參數,用來存儲線程的id號pthread_t tid;pthread_create(&tid, nullptr, mythread, nullptr);// 子線程先指向父線程再打印 保證子線程在父線程后面結束sleep(1);cout << "主線程:" << str << endl;return 0;
}
3.1 創建一個線程
pthread_create
:創建一個線程(啟動一個線程)
PTHREAD_CREATE(3) Linux Programmer's Manual PTHREAD_CREATE(3)NAMEpthread_create - create a new threadSYNOPSIS#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:線程函數,表示線程啟動之后第一時間會執行的函數。也就是說新線程創建之后會指向start_routine函數內任務。@arg:參數,表示新線程要去start_routine執行任務,但是start_routine是一個函數。
start_routine是函數就可以有參數。所有arg實際上就是傳給start_routine的參數的。@return:成功返回0,失敗返回-1,同時errno被設置。*//*int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg){// 其他的代碼// 創建線程去執行start_routinestart_routine(arg);如果線程創建成功return 0;否則就是:return -1;}*/
示例
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;string str = "你好,線程";
// 線程函數,表示線程啟動之后第一時間會執行的函數
void *mythread(void *arg)
{cout << "子線程:" << str << endl;return nullptr;
}int main()
{// 創建線程// 第一次參數,用來存儲線程的id號pthread_t tid;pthread_create(&tid, nullptr, mythread, nullptr);// 子線程先指向父線程再打印 保證子線程在父線程后面結束sleep(1);cout << "主線程:" << str << endl;return 0;
}
3.2 線程的退出
線程函數的退出(線程函數的返回)
void *start_routine(void *arg){return nullptr; // 線程結束
}
在線程執行的任意時刻調用pthread_exit
PTHREAD_EXIT(3) Linux Programmer's Manual PTHREAD_EXIT(3)NAMEpthread_exit - terminate calling threadSYNOPSIS#include <pthread.h>void pthread_exit(void *retval);/*@描述立即結束線程@retval:線程結束之后需要返回的參數,返回值的指針。*/
被別人干掉
-
cancel
:被別人取消(其他線程調用pthread_cancel)-
t1:pthread_cancel(t2)
-
t1
調用取消函數,取消t2,t2
不一定會被取消-
因為
t2
能不能被其他線程取消,取決于t2
線程的一個屬性:取消屬性 -
它是否可以被
cancelled
-
-
PTHREAD_CANCEL(3) Linux Programmer's Manual PTHREAD_CANCEL(3)NAMESYNOPSIS#include <pthread.h>int pthread_cancel(pthread_t thread);/*@描述:取消一個指定的線程@thread:線程號,需要取消的那個線程的id號@return:成功返回0,失敗返回非0.*/pthread_cancel - send a cancellation request to a thread
SYNOPSIS#include <pthread.h>int pthread_cancel(pthread_t thread);/*@描述:取消一個指定的線程@thread:線程號,需要取消的那個線程的id號@return:成功返回0,失敗返回非0.*/
- 這個屬性叫做:可以被取消屬性
- PTHREAD_CANCEL_ENABLE:表示該線程可以被取消
- PTHREAD_CANCEL_DISABLE:表示該線程不能被取消
PTHREAD_SETCANCELSTATE(3) Linux Programmer's Manual PTHREAD_SETCANCELSTATE(3)NAMEpthread_setcancelstate, pthread_setcanceltype - set cancelability state
and typeSYNOPSIS#include <pthread.h>int pthread_setcancelstate(int state, int *oldstate);/*@描述:設置線程取消屬性的@state: 設置線程的取消狀態PTHREAD_CANCEL_ENABLE:表示該線程可以被取消PTHREAD_CANCEL_DISABLE:表示該線程不能被取消@oldstate:線程上一次的取消狀態@return:成功返回0,失敗返回其他值*/
一個線程退出了,并不是所有資源都會釋放。一個線程的退出,它資源釋放全部被釋放,取決于一個屬 性
示例
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;string str = "你好,線程";
// 線程函數,表示線程啟動之后第一時間會執行的函數
void *mythread(void *arg)
{// 存儲的是調用前的狀態int old_state = 0;// 控制當前線程的可取消性 PTHREAD_CANCEL_DISABLE不能被取消 |PTHREAD_CANCEL_ENABLE可以被取消// pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,& old_state);cout << (old_state == PTHREAD_CANCEL_DISABLE? "不可以被取消":"可以被取消") << endl;while (1){cout << "子線程:" << str << endl;sleep(1);}return nullptr;
}int main()
{// 創建線程// 第一次參數,用來存儲線程的id號pthread_t tid;pthread_create(&tid, nullptr, mythread, nullptr);// 子線程先指向父線程再打印 保證子線程在父線程后面結束sleep(3);cout << "主線程:" << str << endl;pthread_cancel(tid);sleep(3);return 0;
}
3.3 資源分離
detach
:分離屬性:
-
ENABLE
: 分離資源屬性- 該線程結束,它的所有資源都會自動釋放。
-
DISABLE
: 不分離資源- 該線程結束,會有部分資源不會自動釋放,需要其他線程調用
pthread_join
這個函數才能完
全釋放
- 該線程結束,會有部分資源不會自動釋放,需要其他線程調用
3.3.1 線程資源回收函數
PTHREAD_JOIN(3) Linux
Programmer's Manual
PTHREAD_JOIN(3)NAMEpthread_join - join with a terminated threadSYNOPSIS#include <pthread.h>int pthread_join(pthread_t thread, void **retval);/*@描述:等待一個指定的線程結束 阻塞狀態@thread:需要等待的線程id號@retval:二級指針,表示線程函數的返回值指針@return:成功返回0失敗返回-1*/
3.3.2 資源分類屬性
PTHREAD_DETACH(3) Linux
Programmer's Manual
PTHREAD_DETACH(3)NAMEpthread_detach - detach a threadSYNOPSIS#include <pthread.h>int pthread_detach(pthread_t thread);/*@描述:設置線程的資源分離屬性@thread:需要設置資源分離屬性的那個線程id@return:成功返回0失敗返回-1*/
3.3.3 獲取自身ID號
#include <pthread.h>pthread_t pthread_self(void);/*
作用:
獲取當前所在線程的tid號
@return:
返回當前所在線程的tid號
*/
示例
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;string str = "你好,線程";
// 線程函數,表示線程啟動之后第一時間會執行的函數
void *mythread(void *arg)
{pthread_detach(pthread_self());while (1){cout << "子線程:" << str << endl;sleep(1);}return nullptr;
}int main()
{// 創建線程// 第一次參數,用來存儲線程的id號pthread_t tid;pthread_create(&tid, nullptr, mythread, nullptr);cout << "主線程:" << str << endl;// 取消線程 pthread_cancel(tid);sleep(3);// 資源回收pthread_join(tid,nullptr);return 0;
}
4. 線程的同步/互斥機制
為了線程之間,能夠去有序的訪問共享資源,引用信號量機制
信號量
:System V
和POSIX 信號量
- 線程互斥鎖
4.1 線程互斥鎖
線程互斥鎖也是信號量
,只不過線程互斥鎖,存在于進程地址空間,用于線程間同步和互斥操作,線程
互斥鎖它的效率相對信號量來說要高。
線程互斥鎖:使用pthread_mutex_t
的類型來描述一個鎖
安裝線程POSIX
幫助手冊:sudo apt-get install manpages-posix-dev // 安裝posix 幫助手冊
- 初始化線程互斥鎖
PTHREAD_MUTEX_INIT(3POSIX) POSIX Programmer's
Manual PTHREAD_MUTEX_INIT(3POSIX)PROLOGThis manual page is part of the POSIX Programmer's Manual. The Linux
implementation of this interface may differ (consult thecorresponding Linux manual page for details of Linux behavior), or the
interface may not be implemented on Linux.NAMEpthread_mutex_init — destroy and initialize a mutexSYNOPSIS#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *restrict mutex,const
pthread_mutexattr_t *restrict attr);/*@描述:初始一個線程互斥鎖@mutex:需要初始化的線程互斥鎖地址pthread_mutex_t mutex; // 創建了一個互斥鎖 &mutex ---> 互斥鎖地址@attr:線程互斥鎖的屬性,一般為NULL,采用默認屬性如:線程的互斥鎖默認設置 1 unlock@return:成功返回0,失敗返回-1*/
- 線程互斥鎖的
PV
操作
int pthread_mutex_lock(pthread_mutex_t *mutex);/*作用:死等上鎖,如果該鎖沒有被釋放,則會一直阻塞在此函數,等待該鎖被釋放。@mutex:需要上鎖的互斥鎖指針@return:成功返回0,表示獲取到了該互斥鎖返回-1,表示獲取出錯,沒有獲取到互斥鎖
*/int pthread_mutex_trylock(pthread_mutex_t *mutex);/*作用: 嘗試上鎖,嘗試性上鎖,如果該鎖沒被釋放,那么立即返回執行后面的代碼。@mutex:需要上鎖的互斥鎖指針@return:成功返回0,表示獲取到了該互斥鎖其他值,表示沒有獲取到互斥鎖
*/int pthread_mutex_timedlock(pthread_mutex_t *mutex,struct timespec
*abs_timeout);
*abs_timeout);/*作用:限時上鎖,如果該鎖沒有被釋放,則會一直阻塞在此函數中一段實際,等待該鎖被釋放。如果時間
過了還沒有被釋放,那么果斷放棄執行后面的代碼。@mutex:需要上鎖的互斥鎖指針@abs_timeout:絕對時間(超時時間),上鎖的時間范圍。@return:成功返回0,表示獲取到了該互斥鎖其他值,表示沒有獲取到互斥鎖*/
- V操作(解鎖操作)
int pthread_mutex_unlock(pthread_mutex_t *mutex);/*
作用:
解鎖線程互斥鎖
@mutex:
需要解鎖的線程互斥鎖指針
*/
- 線程互斥鎖的銷毀操作
int pthread_mutex_destroy(pthread_mutex_t *mutex);/*作用:銷毀一個線程互斥鎖@mutex:需要銷毀的線程互斥鎖指針@return:成功返回0,失敗返回-1*/
5.生產者消費者模型
生產者消費者模型:利用廠商和消費者關系,由生產者線程進行生產(產生任務),再由消費者線程消 費(執行任務),沒有任務的時候,消費者等待生產者產生任務。
- 共享資源的互斥訪問問題
- 信號量/線程互斥鎖
當緩沖區(生產者沒有產出的時候)沒有數據的時候,(消費者)應該怎么辦?
-
不停的去測試,看有沒有數據。
-
輪詢訪問,但是輪詢有缺陷:一直在訪問,浪費CPU資源。輪詢有時間差,占用總線:`Is ``
always
busy
-
-
讓出CPU,當有數據的時候,再喚醒我(
wake up
),線程條件變量:同步
5.1 線程條件變量
線程條件變量:在多線程程序設計中,可以用條件變量
為表示一個特定的條件或者是事件
pthread_cond_t
:來描述一個條件變量(類型)
至于條件變量,到底是一個什么事件或者說表示一個什么條件?完全由程序猿去解釋這個條件變量所代
表的含義。
在條件變量上的三種操作:
- 初始化
- 等待一個條件變量(等待該條件變量所表示的事件)
- 喚醒一個線程/觸發條件變量(喚醒了正在等待該事件的線程)
int data = 0;main : 主線程:生產者:包工頭data = 1;t1:子線程:消費者:牛馬when data == 1;干活data = 0;
5.2 線程條件變量 API
5.2.1 初始化/銷毀條件變量
PTHREAD_COND_DESTROY(3POSIX) POSIX
Programmer's Manual
PTHREAD_COND_DESTROY(3POSIX)PROLOGThis manual page is part of the POSIX Programmer's Manual. The Linux
implementation of this interface may differ (consult the corresponding Linuxmanual page for details of Linux behavior), or the interface may not be
implemented on Linux.NAMEpthread_cond_destroy, pthread_cond_init — destroy and initialize
condition variablesSYNOPSIS#include <pthread.h>int pthread_cond_destroy(pthread_cond_t *cond);/*@描述:銷毀一個條件變量@cond:需要銷毀條件變量指針
*/
int pthread_cond_init(pthread_cond_t *restrict cond,const
pthread_condattr_t *restrict attr);/*@描述:初始化一個條件變量@cond:需要初始化的條件變量的指針@attr:初始化的條件變量的屬性,一般為NULL,采用默認設置@return:成功返回0,失敗返回其他值*/
5.2.2 等待一個條件變量
PTHREAD_COND_TIMEDWAIT(3POSIX) POSIX
Programmer's Manual
PTHREAD_COND_TIMEDWAIT(3POSIX)PROLOGThis manual page is part of the POSIX Programmer's Manual. The Linux
implementation of this interface may differ (consult the corresponding Linuxmanual page for details of Linux behavior), or the interface may not be
implemented on Linux.NAMEpthread_cond_timedwait, pthread_cond_wait — wait on a conditionSYNOPSIS#include <pthread.h>int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t
*restrict mutex,const struct timespec *restrict abstime);/*@描述:限時等待條件變量@cond:需要等待的那個條件變量指針@mutex:線程互斥鎖:為了保護cond所表示的那個事件/共享資源的。條件變量實際也是一個共享資源。@abstime:絕對時間,需要被喚醒的絕對時間*/int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t
*restrict mutex);/*@描述:等待那個條件變量@cond:需要等待的那個條件變量指針@mutex:線程互斥鎖:為了保護cond所表示的那個事件/共享資源的。條件變量實際也是一個共享資源。@abstime:絕對時間,需要被喚醒的絕對時間*/int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t
*restrict mutex);/*@描述:等待那個條件變量@cond:需要等待的那個條件變量指針@mutex:線程互斥鎖:為了保護cond所表示的那個事件/共享資源的。條件變量實際也是一個共享資源。mutex:locked 上鎖pthread_cond_wait(){... 準備工作mutex:unlock 解鎖讓出CPU 等待...when 當條件產生的時候,其他的線程喚醒我的時候mutex:locked 上鎖}@return:成功返回0,被其他線程喚醒失敗返回其他值*/
5.3.3 喚醒線程/觸發條件變量
PTHREAD_COND_BROADCAST(3POSIX) POSIX
Programmer's Manual
PTHREAD_COND_BROADCAST(3POSIX)PROLOGThis manual page is part of the POSIX Programmer's Manual. The Linux
implementation of this interface may differ (consult the corresponding Linuxmanual page for details of Linux behavior), or the interface may not be
implemented on Linux.NAMEpthread_cond_broadcast, pthread_cond_signal — broadcast or signal a
conditionSYNOPSIS#include <pthread.h>// 廣播喚醒int pthread_cond_broadcast(pthread_cond_t *cond);/*@描述:喚醒所有正在等待的線程@cond:那個條件變量@return:成功返回0,失敗返回其他值*/// 單個喚醒int pthread_cond_signal(pthread_cond_t *cond);/*@描述:只喚醒一個線程在等待的線程。@cond:那個條件變量@return:成功返回0,失敗返回其他值*/
注意:廣播喚醒和單個喚醒的區別
- 廣播喚醒:喚醒所有等待的線程,去執行任務,但是任務可能不夠分,那么沒分到的線程繼續休眠
- 單個喚醒:隨機喚醒一個線程執行任務,其他線程繼續休眠。
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>// 全局變量供父子線程使用
int a = 0;
int b = 0;
// 線程互斥鎖
pthread_mutex_t mutex;
// 條件變量
pthread_cond_t cond;
void *myrhtread(void *arg)
{usleep(10);std::cout << "子線程:" << pthread_self() << "啟動完成" << std::endl;while (1){// 上鎖pthread_mutex_lock(&mutex);// 休息,等待主線程給活,沒有就休息等待pthread_cond_wait(&cond, &mutex);//if (a < 0 && b < 0){//小于0時候退先解鎖在退出pthread_mutex_unlock(&mutex);break;}std::cout << "子線程:" << pthread_self() << "計算結果是:" << a + b << std::endl;// 解鎖pthread_mutex_unlock(&mutex);}return nullptr;
}
int main()
{// 初始化互斥鎖pthread_mutex_init(&mutex, nullptr);// 初始化條件變量pthread_cond_init(&cond, nullptr);// 定義五個工作者(牛馬)std::vector<pthread_t> works;// 創建線程for (int i = 0; i < 5; i++){pthread_t tid;pthread_create(&tid, nullptr, myrhtread, nullptr);// 添加到容器里面works.push_back(tid);}usleep(10);// 生產者while (1){// 上鎖pthread_mutex_lock(&mutex);std::cout << "請輸入" << std::endl;std::cin >> a >> b;// 退出條件if(a < 0 && b < 0){pthread_mutex_unlock(&mutex);break;}// 解鎖pthread_mutex_unlock(&mutex);// 喚醒線程可以工作了pthread_cond_signal(&cond);usleep(10);}// 喚醒全部線程逐步退出pthread_cond_broadcast(&cond);// 等待工作者線程結束for(pthread_t tid:works){pthread_join(tid, nullptr);}// 銷毀條件變量pthread_cond_destroy(&cond);// 銷毀鎖pthread_mutex_destroy(&mutex);
}