C++11 多線程庫使用說明

  1. 多線程基礎
    1.1 進程與線程
    根本區別:
    進程是操作系統資源分配的基本單位,線程是任務調度和執行的基本單位

開銷方面:
每個進程都有自己獨立的代碼和數據空間,程序之間的切換開銷較大。
線程可以看作是輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器,線程之間切換開銷小。

所處環境:
一個操作系統能同時運行多個進程(程序)。
在一個進程中,可以有多個線程同時執行。

內存分配方面:
系統在運行的時候會為每個進程分配不同的內存空間。
對線程而言,系統不會為線程分配內存(線程使用的資源,來自于其所屬進程的資源),線程組之間只能共享資源。

包含關系:
沒有線程的進程可以看作是單線程。一個進程可以包含多個線程,每個進程有且只有一個主線程
線程是進程的一部分,所以線程也稱為輕量級進程-

1.2 并發與并行
如果某個系統支持兩個或者多個動作(Action)同時存在,那么這個系統就是一個并發系統。如果某個系統支持兩個或者多個動作同時執行,那么這個系統就是一個并行系統。

并行”概念是“并發”概念的一個子集。也就是說,你可以編寫一個擁有多個線程或者進程的并發程序,但如果沒有多核處理器來執行這個程序,那么就不能以并行方式來運行代碼。因此,凡是在求解單個問題時涉及多個執行流程的編程模式或者執行行為,都屬于并發編程的范疇。

并發是不是一個線程,并行是多個線程?
答:并發和并行都可以是多個線程,就看這些線程能不能同時被(多個)cpu(物理線程)執行,如果可以就是并行,而并發誓多個線程被 cpu 輪流切換著執行。

進程并發
線程并發
1.3 進程通信
同一臺PC:管道,文件,消息隊列,共享內存
不同PC:socket
1.4 線程通訊
鎖機制:包括互斥鎖、條件變量、讀寫鎖
互斥鎖提供了以排他方式防止數據結構被并發修改的方法。
讀寫鎖允許多個線程同時讀共享數據,而對寫操作是互斥的。
條件變量可以以原子的方式阻塞進程,直到某個特定條件為真為止。對條件的測試是在互斥鎖的保護下進行的。條件變量始終與互斥鎖一起使用。

信號量機制(Semaphore)
包括無名線程信號量和命名線程信號量。

信號機制(Signal)
類似進程間的信號處理。

線程間的通信目的主要是用于線程同步,所以線程沒有像進程通信中的用于數據交換的通信機制。

1.5 線程并發
一個進程中的所有線程共享地址空間,因此全局變量,指針,引用可以在線程之間傳遞
數據一致性問題
線程之間切換需要保存中間量,消耗資源,所以線程不是越多越好,極限差不多在 2000 個線程,或者根據硬件,比如 CPU*2,或者根據業務需要,實際中一般不操作500個,控制在 200 個以內,否則效率太低。
2. C++ 11 多線程庫
c++ 11 開始語言本身提供多線程支持,因此可以實現跨平臺,可移植性。

2.1 創建線程

include

初始函數,函數結束,他的所有線程結束
主線程結束,它的子線程結束
#include
#include

using namespace std;

// 入口函數
void entry(int a)
{
cout << “entry sub-thread” << endl;
}

int main()
{
thread threadObj(entry, 6); //入口函數,參數
threadObj.join(); //等待線程結束
// threadObj.detch();
cout << “entry main thread” << endl;
return 0;
}

類的成員函數作為入口函數

class A {
public:
A(int i) : m_i(i)
{
cout << "construct! thread ID: " << std::this_thread::get_id() << endl;
}

void print(const A& a)
{cout << "sub_thread ID: " << std::this_thread::get_id() << endl;
}
int m_i;

};

int main()
{
int n = 1;
int& m = n;
A a(10);
cout << "main thread ID: " << std::this_thread::get_id() << endl;

thread mythread(&A::print, &a, a);  //傳入成員函數地址、 類對象地址、參數
mythread.join();
return 0;

}

2.2 thread 的入口
可調用對象(普通函數,類成員函數,類靜態函數,仿函數,函數指針,重載了operate ()的類對象,lambda表達式,std::function)
class 需要是可調用的類,即 void operator ()(), 注意以對象作為入口,對象會被復制到子線程中
2.3 thread 入口參數
引用 vs實測背后發生了copy,所以無法通過引用來傳值,需要注意的是,如果用引用需要同時用const,比如const A& a, 否則會報錯,如果需要通過引用傳值,需要用std::ref(或者加 & )
void print(const int& n) //沒有const會報錯
{
cout << "sub_thread: " << n << endl;
}

int main()
{
int n = 1;
int& m = n;

thread mythread(print, m); //雖然print參數是引用,m 是引用但是會發生拷貝
//thread mythread(print, std::ref(m)) //引用
mythread.join();
return 0;

}

指針 不安全,可能主線程已經銷毀了內存,造成隱患,detach時一定會出問題

臨時參數(對象)可以幫助解決主線程退出的問題,即主線程退出之前會先構造好臨時對象,具體來說,在創建線程時就構造臨時對象,然后在線程入口函數里面用引用來接(否則會多一次拷貝構造)

如果用隱式類型轉換會有風險,因為隱式轉換會在子線程中完成,如果detach的話,就會線程不安全

如果參數是智能指針,如unique_ptr, 需要用std::move(your unique_ptr), 但是一定要用join,因為內存是共享的,否則會不安全

成員函數指針

2.4 多個線程下保護共享數據
2.4.1 mutex
#include “stdafx.h”
#include
#include
#include
#include
#include

using namespace std;

class Msg {
public:
void InMsg()
{
for(int i = 0; i < 1000; ++i)
{
cout << "start input msg id = " << i << endl;
mut_Msg.lock();
m_Msg.push_back(i);
mut_Msg.unlock();
cout << "end input msg id = " << i << endl;
}
}

void OutMsg()
{while (1){if (bOutMsg()){cout << "pop out msg success!" << endl;}else{cout << "msg box is empty!" << endl;_sleep(1000);}}
}bool bOutMsg()
{mut_Msg.lock();if (!m_Msg.empty()){m_Msg.pop_front();mut_Msg.unlock();return 1;}else{mut_Msg.unlock();return 0;}
}

private:
list m_Msg;
mutex mut_Msg;
};

int main(void)
{
Msg a;
thread thread1(&Msg::InMsg, &a);
thread thread2(&Msg::OutMsg, &a);
thread1.join();
thread2.join();
return 0;
}

mutex 使用時應該盡量只保護需要保護的代碼段。
unlock 不能丟
2.4.2 lockguard
可以用 lockguard 接管 mutex,這樣就不用手動 unlock, 傳入 std::adopt_lock 參數就是告訴 lockguard,鎖已經鎖了,只需要管理 unlock 就可以了。

lockguard 實際上是在其構造函數中調用了 lock(), 析構函數中調用 unlock().

std::lock(mutex1, mutex2);
std::lockguardstd::mutex guard1(mutex1, std::adopt_lock)
std::lockguardstd::mutex guard2(mutex2, std::adopt_lock)
//…

lockguard 沒有提供手動 lock & unlock 的接口。

2.4.3 死鎖
死鎖,兩個或以上的 lock 可能出現死鎖。

threadA
{
mutexA.lock();
mutexB.lock();
//do some thing
mutexA.unlock();
mutexB.unlock();
}

threadB
{
mutexB.lock();
mutexA.lock();
//do some thing
mutexA.unlock();
mutexB.unlock();
}

解決方法:

可以通過控制不同的線程lock的順序來避免
std::lock() 同時鎖多個鎖,如果有一個鎖不上,它會 unlock 已經 lock 的鎖
std::lock(mutex1, mutex2);
//…
mutex1.unlock();
mutex2.unlock();

2.4.4 unique_lock
unique_lock 比 lockguard 更靈活,但是效率要低一些,占用內存更多。

unique_lock 可以取代 lockguard,但是相比 lockguard,有更豐富的一些功能。

unique_lock 支持以下參數:

adopt_lock,意義和 lockguard 中一樣表示已經lock了
try_to_lock, 意味著 lock 和 unlock 都自動管理,可以判斷是否lock成功,做不同操作,所以不會卡住。
std::unique_lockstd::mutex guard1(mutex1, std::try_to_lock);
if(guard1.owns_lock()) // get lock
{
//…
}
else
{
//…
}

defer_lock, 初始化一個沒有加鎖的 lock, 可以手動 lock,手動或者自動 unlock,
手動 lock 和 unlock 可以直接調用 unique_lock 的成員函數:lock(), unlock()
unique_lock 可以通過 release 來釋放資源,即不再關聯mutex。

unique_lock 和 lock_guard 都不能復制,但是unique_lock 的所有權可以轉移。

std::unique_lockstd::mutex guard1(_mu);
std::unique_lockstd::mutex guard2 = guard1; // error
std::unique_lockstd::mutex guard2 = std::move(guard1); // ok

2.5 線程安全的單例模式
#include
#include
#include

using namespace std;

std::mutex instance_mutex;

class SP {
private:
SP() {}
static SP *m_pInstance; //static使得 m_pInstance 的作用域到程序結束

class FREE {	//這個class專門負責 delete
public:~FREE(){if (SP::m_pInstance){delete SP::m_pInstance;SP::m_pInstance = NULL;}}
};

public:
static SP* GetInstance() //static,否則無法直接調用
{
if (m_pInstance == NULL) //雙重鎖定,提高運行效率,減少不必要的lock,unlock
{
std::unique_lockstd::mutex mutex1(instance_mutex); //c++ 11,自動lock,unlock
if (m_pInstance == NULL)
{
m_pInstance = new SP();
static FREE f; //static 表示作用域直到程序推出,也就是說程序退出時會調用析構函數,從而達到自動釋放內存的作用
}
return m_pInstance;
}
}

static void Free()	//手動 delete
{if (m_pInstance){delete m_pInstance;m_pInstance = NULL;}
}

};

SP* SP::m_pInstance = NULL;

int main(void)
{
SP *p1 = SP::GetInstance();
SP *p2 = SP::GetInstance();
SP::Free();
SP::Free();
p1->Free();
p2->Free();
}

2.6 call_once()
std::call_once() 是 c++ 11 引入的函數,保證某個函數只執行一次,具備互斥量的功能,但是比 mutex 高效, 適合比如 init 等場合

std::once_flag g_flag //決定 call_once 是否調用function

std::call_once(g_flag, function_with_code_only_call_once)

2.7 condition_variable
利用 condition_variable 一般用來等待 unique_lock, 可以提高程序執行效率。

condition_variable 類有三個成員函數

wait() 等待一個條件成立
notify_one() 隨機喚醒一個正在 wait 的線程,如果線程沒有阻塞在 wait() 處,則沒有辦法喚醒
notify_all() 喚醒所有等待的線程
//thread A
std::unique_lockstd::mutex lock1(mutex1);

//do some thing

condition1.notify_one();
//condition1.notify_all();

//thread B
std::unique_lockstd::mutex lock1(mutex1);

//do some thing

condition1.wait(lock1); //如果沒有第二個參數,wait將 release mutex1,然后阻塞,等待被喚醒
//condition1.wait(lock1, [this]{ //第二個參數可以是任何可調用對象,如果表達式返回ture,直接return,如果為fasle,同上
if(m_bStatus)
{
return true;
}
return false;
})

虛假喚醒
線程被notify,但是卻并沒發執行,比如上面的例子,m_bStatus 為false, 所以通過第二個參數可以防止虛假喚醒。
2.8 std::async, std::future
之前通過 thread 來創建線程,如果需要返回結果,可以通過全局變量/引用來實現,這里是另一種方式。

async 用于啟動一個異步任務(創建線程并執行入口函數),返回一個 future 對象。通過 future 對象的 get() 獲取入口函數返回的結果。

int entry()
{
//。。。
return 1;
}

int main()
{
std::future result = std::async(entry); //entry 開始執行
//std::future result = std::async(std::launch::async, entry); //效果同上
//std::future result = std::async(std::launch::deferred, entry); //延遲創建,等待 get/wait 才開始執行,如果沒有調用,不會創建子線程
int re = reult.get(); //get() 時會等待 entry 執行完, get() 不能調用多次
//result.wait(); //不獲取值返回值,等待線程
}

2.9 std::packaged_task
包裝可調用對象,方便作為線程入口調用。

int entry(int a)
{
//。。。
return a;
}

int main()
{
std::packaged_task<int(int)> pt(entry); //pt 本身就是一個可調用對象,類似函數,可以直接 pt(10),調用
std::thread thread1(std::ref(pt), 1);
thread1.join()

std::future<int> result = pt.get_future(); //result 保存返回結果,可以 get
//。。。

}

2.10 std::promise
可以通過promise在線程直接傳遞值,一個線程往 promise 對象中寫值,在其他線程中取值

void entry(std::promise &prom, int a)
{
//。。。
prom.set_value(result)
return;
}

void entry(std::future & f)
{
//。。。
result = f.get();
return;
}

int main()
{
std::promise prom;
std::thread t1(entry, std::ref(prom), 10);
t1.join()

std::future<int> result = prom.get_future(); //result 保存返回結果,可以 get//。。。

}

2.11 atomic
atomic 作用和 mutex 類似,不同點是 mutex 針對一個代碼段,而 atomic 針對一個變量。
atomic 操作相比 mutex 效率更高。

int g_count = 0;
//mutex
void entry()
{
mutex1.lock();
g_count++;
mutex1.unlock;
}

std::atomic g_count = 0;
//atomic
void entry()
{
g_count++;
}

2.12 windows 臨界區
windows 臨界區的概念和 mutex 類似。另外多次進入臨界區是OK的,但是需要調用對應次數的出臨界區。mutex 是不允許同一個線程中多次 lock 的。

include <windows.h>

CRITICAL_SECTION winsec
InitializeCriticalSection(winsec) //使用前必須初始化

EnterCriticalSection(&winsec);
EnterCriticalSection(&winsec);
//do some thing
LeaveCriticalSection(&winsec);
LeaveCriticalSection(&winsec);

2.13 線程池
#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include
#include
#include
#include
#include
#include <condition_variable>
#include
#include
#include

// 線程池類
class ThreadPool {
public:
// 構造函數,傳入線程數
ThreadPool(size_t threads);
// 入隊任務(傳入函數和函數的參數)
template<class F, class… Args>
auto enqueue(F&& f, Args&&… args)
->std::future<typename std::result_of<F(Args…)>::type>;
// 一個最簡單的函數包裝模板可以這樣寫(C++11)適用于任何函數(變參、成員都可以)
// template<class F, class… Args>
// auto enqueue(F&& f, Args&&… args) -> decltype(declval()(declval()…))
// { return f(args…); }
// C++14更簡單
// template<class F, class… Args>
// auto enqueue(F&& f, Args&&… args)
// { return f(args…); }

// 析構
~ThreadPool();

private:
// need to keep track of threads so we can join them
// 工作線程組
std::vector< std::thread > workers;
// 任務隊列
std::queue< std::function<void()> > tasks;

// synchronization 異步
std::mutex queue_mutex;	// 隊列互斥鎖
std::condition_variable condition;	// 條件變量
bool stop;	// 停止標志

};

// the constructor just launches some amount of workers
// 構造函數僅啟動一些工作線程
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for (size_t i = 0; i<threads; ++i)
// 添加線程到工作線程組
workers.emplace_back( // 與push_back類型,但性能更好(與此類似的還有emplace/emlace_front)
[this]
{ // 線程內不斷的從任務隊列取任務執行
for (;😉
{
std::function<void()> task;

		{// 拿鎖(獨占所有權式)std::unique_lock<std::mutex> lock(this->queue_mutex);// 等待條件成立this->condition.wait(lock,[this] { return this->stop || !this->tasks.empty(); });// 執行條件變量等待的時候,已經拿到了鎖(即lock已經拿到鎖,沒有阻塞)// 這里將會unlock釋放鎖,其他線程可以繼續拿鎖,但此處任然阻塞,等待條件成立// 一旦收到其他線程notify_*喚醒,則再次lock,然后進行條件判斷// 當[return this->stop || !this->tasks.empty()]的結果為false將阻塞// 條件為true時候解除阻塞。此時lock依然為鎖住狀態// 如果線程池停止或者任務隊列為空,結束返回if (this->stop && this->tasks.empty()) {return;}// 取得任務隊首任務(注意此處的std::move)task = std::move(this->tasks.front());// 從隊列移除this->tasks.pop();}// 執行任務task();}
}
);

}

// add new work item to the pool
// 添加一個新的工作任務到線程池
template<class F, class… Args>
auto ThreadPool::enqueue(F&& f, Args&&… args)
-> std::future<typename std::result_of<F(Args…)>::type>
{
using return_type = typename std::result_of<F(Args…)>::type;

// 將任務函數和其參數綁定,構建一個packaged_task
auto task = std::make_shared< std::packaged_task<return_type()> >(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
// 獲取任務的future
std::future<return_type> res = task->get_future();
{// 獨占拿鎖std::unique_lock<std::mutex> lock(queue_mutex);// don't allow enqueueing after stopping the pool// 不允許入隊到已經停止的線程池if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}// 將任務添加到任務隊列tasks.emplace([task]() { (*task)(); });
}
// 發送通知,喚醒某一個工作線程取執行任務
condition.notify_one();
return res;

}

// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
{
// 拿鎖
std::unique_lockstd::mutex lock(queue_mutex);
// 停止標志置true
stop = true;
}
// 通知所有工作線程,喚醒后因為stop為true了,所以都會結束
condition.notify_all();
// 等待所有工作線程結束
for (std::thread &worker : workers) {
worker.join();
}
}

#endif

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

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

相關文章

005 vim程序編輯器

1. 為何要學 vim 所有的 Unix Like 系統都會內建 vi 文本編輯器&#xff0c;其他的文書編輯器則不一定會存在&#xff1b;很多個別軟件的編輯接口都會主動呼叫 vi (例如未來會談到的 crontab, visudo, edquota 等指令)&#xff1b;vim 具有程序編輯的能力&#xff0c;可以主動…

Redis 實踐筆記1---基礎知識

前言 由于Redis的廣泛使用&#xff0c;加上在項目中涉及到Redis&#xff0c;因此會根據自己的使用和學習&#xff0c;寫一個Redis系列的博客&#xff0c;作為自己的筆記&#xff0c;同時也分享給大家。 What is Redis ? 看一下Redis的官網http://redis.io/對REDIS的描述&#…

[操作系統實驗lab4]實驗報告

實驗概況 在開始實驗之前&#xff0c;先對實驗整體有個大概的了解&#xff0c;這樣能讓我們更好地進行實驗。 我們本次實驗需要補充的內容包括一整套以sys開頭的系統調用函數&#xff0c;其中包括了進程間通信需要的一些系統調用如sys_ipc_can_recv等&#xff0c;以及補充完成f…

設計模式C++實踐

1、單例模式 1&#xff09;餓漢士單例模式 2&#xff09;懶漢士單例模式

js中怎么寫自執行函數

<!DOCTYPE html><html lang"en"><head> <meta charset"UTF-8"> <title></title> <script type"text/javascript"> /** * Window 是一個非常重要的對象 */ c…

最優化課堂筆記06-無約束多維非線性規劃方法(含重點)

引言 6.1 坐標輪換法&#xff08;工程上基本不用&#xff0c;效率低不適用高維&#xff09; 6.1例子&#xff1a;主要是對最優步長alpha的確定 6.2 最速下降法&#xff08;相鄰兩次的搜索方向互相垂直&#xff09; 6.2例子 求解法一 注&#xff1a;最速下降法與坐標輪換法的區…

WINHEX的比較、同步功能加上NTFS對稀疏文件的支持

[原創]如何快速地分析RAID信息在每塊盤上的記錄方式&#xff0c;如何快速地確定系統的實質讀寫操作。WINHEX是一個非常好的軟件&#xff0c;通過其比較和同步功能加上NTFS對稀疏文件的支持&#xff0c;看看怎么實現上述設想。。。我們會有這樣的需求&#xff1a;在RAID上的幾塊…

Z表數據EXCEL導入

很多項目都有這種需求&#xff0c;雖然別人用的各有不同&#xff0c;不過閑來無事&#xff0c;還是自己搞了一個出來。基于EXCEL的導入。 *&---------------------------------------------------------------------* *& Report ZLY_UPLOAD_TABLE *& *&------…

運動軌跡規劃算法專欄

1、全局局部路徑規劃&#xff0c;解決低速簡單障礙物環境的路徑規劃功能

現代制造工程課堂筆記06-集成電路制造工程

中國主要是進行了集成電路的下游環節&#xff0c;即是封裝與測試

悖論:早期互聯網項目,是否需要技術含量?

在自己創業、看別人創業、和別人一起創業的過程中&#xff0c;一直有個“悖論”困惑著我&#xff0c;讓我很不舒服。因為如鯁在喉&#xff0c;所以不吐不快。悖論 早期互聯網項目&#xff0c;通常來說&#xff0c;技術難度不算大&#xff1f; 從創業者角度來講&#xff0c;最…

資源打包后項目中的文件

一.資源打包Assets.car1.如果部署版本>8.0,并且圖片被放入到Images.xcassets,圖片打包之后會被放到Assets.car,并且是有對圖片資源進行壓縮.2.如果部署版本<8.0,并且圖片被放入到Images.xcassets,圖片會被放到MainBundle里面,并且不會對圖片進行壓縮處理.3.如果圖片是直接…

現代制造工程-考試復習02

1.說明&#xff1a;標有重點的是會出簡答題或者計算題&#xff0c;而未標注的則是會出選擇題和填空題 2.題型&#xff1a;判斷、選擇、簡答、論述

VINS狀態估計篇-視覺sfm初始化

1、通過判斷當前幀和滑動窗口中&#xff0c;平均視差>30

TP_字母函數

1. A() A函數用于實例化控制器 格式&#xff1a;[資源://][模塊/]控制器 A($name,$layer,$level) param string $name 資源地址 param string $layer 控制層名稱 param integer $level 控制器層次 return Controller|false 2. B() 執行某個行為 B($name,$tag,&$paramsNULL)…

父div高度和寬度的應用

這是我自己在仿騰訊首頁時遇到的布局問題&#xff0c;在此記錄&#xff0c;如果有錯&#xff0c;歡迎指正。 首先是對齊問題&#xff0c;可以把父div的高度設置為0&#xff0c;然后調整padding值&#xff0c;這樣可以批量調整子div們和其他父div的相對高度。 這是父div的樣式 .…

flume package遇到的問題

flume打包遇到的一些問題 1.ipc兼容性問題&#xff0c;線上使用2.3.0的hdfs&#xff0c;但是打包時默認為1.2.1的 123408 Apr 2015 19:38:25,122 WARN [SinkRunner-PollingRunner-DefaultSinkProcessor] (org.apache.flume.sink.hdfs.HDFSEventSink.process:455) - HDFS IO e…

有限元筆記06-三維實體單元

多個四面體單元組合起來可以形成六面體

python讀取excle表格數據,將數據編輯到圖像上工程

這里寫目錄標題一級目錄&#xff1a;python讀取excel表格內容二級目錄&#xff1a;python如何分割字符串三級目錄&#xff1a;python如何在圖像的相應位置編輯文字或者字母、數字一級目錄&#xff1a;python讀取excel表格內容 二級目錄&#xff1a;python如何分割字符串 三級…