LES06 :C++线程与智能指针
C++線程與智能指針
文章目錄
- C++線程與智能指針
- 線程
- C++11線程
- POSIX線程
- 線程屬性
- 分離線程
- 調度策略與優先級
- 線程同步
- 條件變量
- 智能指針
- shared_ptr
- weak_ptr
- unique_ptr
- 自定義智能指針
- 部分C++11、14特性
- nullptr
- 類型推導
- 基于范圍的 for 循環
- Lambda
- 作業(后面FFmpeg直播播放器課程的隊列工具類)
線程
線程,有時被稱為輕量進程,是程序執行的最小單元。
C++11線程
#include <thread>void task(int i) {cout << "task:" << i << endl; }thread t1(task,100); //等待線程結束再繼續執行 t1.join();POSIX線程
POSIX 可移植操作系統接口,標準定義了操作系統應該為應用程序提供的接口標準
Windows上使用 配置: 下載
cmake_minimum_required (VERSION 3.8) include_directories("XXX/pthreads-w32-2-9-1-release/Pre-built.2/include") #設置變量為x64 or x86 if(CMAKE_CL_64)set(platform x64) else()set(platform x86) endif() link_directories("XXX/pthreads-w32-2-9-1-release/Pre-built.2/lib/${platform}") #如果出現 “timespec”:“struct” 類型重定義 設置 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_STRUCT_TIMESPEC") # 將源添加到此項目的可執行文件。 add_executable (lsn6example "lsn6_example.cpp" "lsn6_example.h") target_link_libraries(lsn6example pthreadVC2)32位拷貝pthreadVC2.dll 到windows/syswow64目錄
64位拷貝pthreadVC2.dll 到windows/system32目錄
#include <pthread.h> void *pthreadTask(void* args) {int* i = static_cast<int*>(args);cout << "posix線程:" << *i << endl;return 0; } pthread_t pid; int pi = 100; pthread_create(&pid, 0, pthreadTask, &pi); //等待線程的結束 pthread_join(pid,0);線程屬性
線程具有屬性,用 pthread_attr_t 表示
pthread_attr_t attr; //初始化 attr中為操作系統實現支持的線程所有屬性的默認值 pthread_attr_init(&attr); pthread_attr_destroy(&attr);分離線程
線程創建默認是非分離的,當pthread_join()函數返回時,創建的線程終止,釋放自己占用的系統資源
分離線程不能被其他線程等待,pthread_join無效,線程自己玩自己的。
//設置是否為分離線程 //PTHREAD_CREATE_DETACHED 分離 //PTHREAD_CREATE_JOINABLE 非分離 pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);調度策略與優先級
Windows 無法設置成功
//設置調度策略 //返回0 設置成功 pthread_attr_setschedpolicy(&attr, SCHED_FIFO); // SCHED_FIFO // 實時調度策略,先到先服務 一旦占用cpu則一直運行。一直運行直到有更高優先級任務到達或自己放棄。 // SCHED_RR // 實時調度策略,時間輪轉 系統分配一個時間段,在時間段內執行本線程//設置優先級 //獲得對應策略的最小、最大優先級 int max = sched_get_priority_max(SCHED_FIFO); int min = sched_get_priority_min(SCHED_FIFO); sched_param param; param.sched_priority = max; pthread_attr_setschedparam(&attr, ¶m);線程同步
多線程同時讀寫同一份共享資源的時候,可能會引起沖突。需要引入線程“同步”機制,即各位線程之間有序地對共享資源進行操作。
#include <pthread.h> using namespace std;queue<int> q; void *pop(void* args) {//線程未同步導致的多線程安全問題// 會有重復的數據取出并出現異常if (!q.empty()){printf("取出數據:%d\n", q.front());q.pop();}else {printf("無數據\n");}return 0; }int main() {for (size_t i = 0; i < 5; i++){q.push(i);}pthread_t pid[10];for (size_t i = 0; i < 10; i++){pthread_create(&pid[i], 0, pop, &q);}system("pause");return 0; }加入互斥鎖
queue<int> q; pthread_mutex_t mutex; void *pop(void* args) {// 鎖pthread_mutex_lock(&mutex);if (!q.empty()){printf("取出數據:%d\n", q.front());q.pop();}else {printf("無數據\n");}// 放pthread_mutex_unlock(&mutex);return 0; }int main() {//初始化互斥鎖pthread_mutex_init(&mutex, 0);for (size_t i = 0; i < 5; i++){q.push(i);}pthread_t pid[10];for (size_t i = 0; i < 10; i++){pthread_create(&pid[i], 0, pop, &q);}//需要釋放for (size_t i = 0; i < 10; i++){pthread_join(pid[i], 0);}pthread_mutex_destroy(&mutex);system("pause");return 0; }條件變量
條件變量是線程間進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立",從而喚醒掛起線程
template <class T> class SafeQueue { public:SafeQueue() {pthread_mutex_init(&mutex,0);}~SafeQueue() {pthread_mutex_destory(&mutex);}void enqueue(T t) {pthread_mutex_lock(&mutex);q.push(t);pthread_mutex_unlock(&mutex);}int dequeue(T& t) {pthread_mutex_lock(&mutex);if (!q.empty()){t = q.front();q.pop();pthread_mutex_unlock(&mutex);return 1;}pthread_mutex_unlock(&mutex);return 0;}private:queue<T> q;pthread_mutex_t mutex; };上面的模板類存放數據T,并使用互斥鎖保證對queue的操作是線程安全的。這就是一個生產/消費模式。
如果在取出數據的時候,queue為空,則一直等待,直到下一次enqueue加入數據。
這就是一個典型的生產/消費模式, 加入條件變量使 “dequeue” 掛起,直到由其他地方喚醒
#pragma once #include <queue> using namespace std;template <class T> class SafeQueue { public:SafeQueue() {pthread_mutex_init(&mutex,0);pthread_cond_init(&cond, 0);}~SafeQueue() {pthread_mutex_destory(&mutex);pthread_cond_destory(&cond);}void enqueue(T t) {pthread_mutex_lock(&mutex);q.push(t);//發出信號 通知掛起線程//由系統喚醒一個線程//pthread_cond_signal(&cond);// 廣播 對應多個消費者的時候 多個線程等待喚醒所有pthread_cond_broadcast(&cond);pthread_mutex_unlock(&mutex);}int dequeue(T& t) {pthread_mutex_lock(&mutex);//可能被意外喚醒 所以while循環while (q.empty()){pthread_cond_wait(&cond, &mutex);}t = q.front();q.pop();pthread_mutex_unlock(&mutex);return 1;}private:queue<T> q;pthread_mutex_t mutex;pthread_cond_t cond; }; #include "lsn6_example.h" #include <thread> #include <pthread.h>using namespace std; #include "safe_queue.h"SafeQueue<int> q;void *get(void* args) {while (1) {int i;q.dequeue(i);cout << "消費:"<< i << endl;}return 0; } void *put(void* args) {while (1){int i;cin >> i;q.enqueue(i);}return 0; } int main() {pthread_t pid1, pid2;pthread_create(&pid1, 0, get, &q);pthread_create(&pid2, 0, put, &q);pthread_join(pid2,0);system("pause");return 0; }智能指針
自C++11起,C++標準庫提供了兩大類型的智能指針
shared_ptr
操作引用計數實現共享式擁有的概念。多個智能指針可以指向相同的對象,這個對象和其相關資源會在最后一個被銷毀時釋放。
class A { public:~A() {cout << "釋放A" << endl;} };void test() {//自動釋放 引用計數為1shared_ptr<A> a(new A());//退出方法 shared_ptr a本身釋放,對內部的 A 對象引用計數減1 則為0 釋放new 出來的A 對象 }雖然使用shared_ptr能夠非常方便的為我們自動釋放對象,但是還是會出現一些問題。最典型的就是循環引用問題。
class B; class A { public:~A() {cout << "釋放A" << endl;}shared_ptr<B> b; };class B { public:~B() {cout << "釋放B" << endl;}shared_ptr<A> a; }; void test() {//自動釋放shared_ptr<A> a(new A()); //A引用計數為1shared_ptr<B> b(new B()); //B引用計數為1cout << a.use_count() << endl; //查看內部對象引用計數a->b = b; //A 引用計數為2b->a = a; //B 引用計數為2//退出方法,a釋放,A引用計數-1結果為1 不會釋放 B也一樣 }weak_ptr
weak_ptr是為配合shared_ptr而引入的一種智能指針。主要用于觀測資源的引用情況。
它的構造和析構不會引起引用記數的增加或減少。沒有重載*和->但可以使用lock獲得一個可用的shared_ptr對象。
配合shared_ptr解決循環引用問題
class B; class A { public:~A() {cout << "釋放A" << endl;}weak_ptr<B> b; }; class B { public:~B() {cout << "釋放B" << endl;}weak_ptr<A> a; };void test() {//自動釋放shared_ptr<A> a(new A()); //A引用計數為1shared_ptr<B> b(new B()); //B引用計數為1a->b = b; //weak_ptr 引用計數不增加b->a = a; //weak_ptr 引用計數不增加//退出方法,A B釋放 }weak_ptr 提供expired 方法等價于 use_count == 0,當expired為true時,lock返回一個存儲空指針的shared_ptr
unique_ptr
實現獨占式引用,保證同一時間只有一個智能指針指向內部對象。
unique_ptr<A> a(new A());auto_ptr已經不推薦使用
自定義智能指針
template <typename T> class Ptr { public:Ptr() {count = new int(1);t = 0;}Ptr(T *t):t(t) {//引用計數為1count = new int(1);}~Ptr() {//引用計數-1 為0表示可以釋放T了if (--(*count) == 0){if (t) {delete t;}delete count;t = 0;count = 0;}}//拷貝構造函數Ptr(const Ptr<T> &p) {//引用計數+1++(*p.count);t = p.t;count = p.count;}Ptr<T>& operator=(const Ptr<T>& p) {++(*p.count);//檢查老的數據是否需要刪除if (--(*count) == 0) {if (t) {delete t;}delete count;}t = p.t;count = p.count;return *this;}//重載-> 操作T 類T* operator->() { return t; }private:T *t;int *count; };重載=為什么返回引用,而不是對象?
return *this后馬上就調用拷貝構造函數,將*this拷貝給一個匿名臨時對象,然后在把臨時對象拷貝給外部的左值(a=b,a為左值),再釋放臨時對象。這樣首先會造成不必要的開銷。
同時如果沒有自定義的拷貝函數,則會使用默認的淺拷貝。如果類中存在指向堆空間的成員指針,需要進行深拷貝(重新申請內存),避免相同內存地址被其他地方釋放導致的問題。
如果此處返回對象不會導致出現問題:
第四節課中提到:
引用類型(Test1&) 沒有復制對象 返回的是 t 對象本身 t會被釋放 所以會出現問題(數據釋放不徹底就不一定)
這里的t作用域是函數內我們自己創建的一個臨時對象,因此會被釋放,而在此處返回 *this,作用范圍和t不一樣。其次,在類中定義了拷貝函數,雖然未進行深拷貝,但小心的維護了堆內存指針(t、count),不會出現堆內存釋放導致的懸空指針情況。
部分C++11、14特性
nullptr
nullptr 出現的目的是為了替代 NULL。 C++11之前直接將NULL定義為 0。
void test(int* i){} void test(int i){} //現在調用哪一個test? test(int) test(NULL); //調用test(int* i) test(nullptr);類型推導
C++11 重新定義了auto 和 decltype 這兩個關鍵字實現了類型推導,讓編譯器來操心變量的類型。
auto i = 5; // i 被推導為 int auto p = new auto(10) // arr 被推導為 int * //但是auto不能作用在數組上 auto arr1[10] = { 0 }; //錯誤 auto arr2= {0}; //正確 typeid(arr2).name() //獲得類型名為 initializer_list(后續介紹) // int j decltype(i) j = 10;基于范圍的 for 循環
實際上就是foreach
vector<int> vec = { 1,2,3,4,5 }; //配合auto使用 for(auto i : vec) {cout << i << endl; }Lambda
匿名函數,即沒有函數名的函數
完整形式:
[捕獲外部變量列表 ] (參數列表) mutable exception->返回類型 { 函數體 }
mutable:在外部變量列表以值來捕獲時,無法修改變量的值,加上mutable表示可修改(不會影響外部變量)
auto i = 5; // [&] 表示外部變量都以引用的形式在lambda中使用,函數內部修改i的值會影響外部 // 這里的 -> auto 自動推導在c++11不支持,c++14中對auto進行了擴展 thread t1([&] () -> auto {i = 100;cout << "線程:" << i << endl; }); _sleep(10); cout << i << endl;| [] | 不捕獲任何外部變量 |
| [i, …] | 以值得形式捕獲指定的多個外部變量(用逗號分隔);如果引用捕獲,需要顯示聲明& |
| [this] | 以值的形式捕獲this指針 |
| [=] | 以值的形式捕獲所有外部變量 |
| [&] | 以引用形式捕獲所有外部變量 |
| [=, &x] | 變量x以引用形式捕獲,其余變量以傳值形式捕獲 |
| [&, x] | 變量x以值的形式捕獲,其余變量以引用形式捕獲 |
作業(后面FFmpeg直播播放器課程的隊列工具類)
封裝queue滿足:
1、滿足同一個類存放AvFrame*與AvPacket*兩種結構
? 知識點:模板類
? AvFrame:存儲解碼的數據 創建:av_frame_alloc、釋放:av_frame_free
? AvPacket:存儲壓縮的數據 創建:av_packet_alloc、釋放:av_packet_free
2、滿足動態設置釋放函數(AvFrame與AvPacket釋放函數不同)
? 知識點:函數指針 函數引用傳值
3、保證線程安全同時取出數據時保證一定能夠取出
可用pts賦值 1、2、3…測試
總結
以上是生活随笔為你收集整理的LES06 :C++线程与智能指针的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python从入门到精通--课程目录
- 下一篇: 马尔科夫蒙特卡罗方法