C++11線程

C++11提供了線程庫,下面我們來看一下如何使用。

線程的創建

頭文件

要創建一個線程需要包一個線程頭文件:#include <thread>

我們先來看看thread支持的構造方式。

支持默認構造,直接使用thread創建一個空的線程對象。

也支持帶參的構造,參數就是可執行的對象,可以使函數指針、仿函數、lamdba表達式、包裝器,參數是可執行對象需要傳的參數,因為是可變參數,因此根據實際的可執行對象傳遞參數個數即可。

線程是不支持拷貝構造的,不可以用一個線程對象拷貝另一個線程對象,但是支持移動構造。

無參構造 + 移動賦值

我們創建線程的時候可以先不給該線程關聯函數,創建一個空線程,等到后續有需要的時候在關聯函數,比如實現線程池就可以這樣做,線程池的線程是不知道要執行啥函數的。

直接使用 thread 這個類型,thread是一個類,里面實現了線程的各種方法,使用thread然后后面跟上變量名,這樣創建的線程是沒有啟動的線程,還沒有給該線程關聯要執行的函數,可以使用移動賦值來給該線程關聯要執行的函數,使用匿名構造,第一個參數傳遞要執行的函數,后續的參數是函數要傳遞的參數,如果函數有一個參數就傳遞1個有兩個參數就傳遞2個。線程的構造函數是一個函數對象和可變參數列表。

我們創建好線程之后需要進行等待,直接使用thread的join函數即可,該函數沒有返回值和參數。

get_id()可以獲取線程的id,這個函數在this_thread這個命名空間里。因此使用時需要使用這個空間域。

#include <iostream>
#include <thread>
using namespace std;void func(int x)
{cout << this_thread::get_id() << endl;for(int i = 1; i <= x; i++)cout << "music" << endl;
}
int main()
{// 無參構造 + 移動賦值thread t1; // 沒啟動的線程t1 = thread(func, 9);// 線程等待t1.join();return 0;
}

總結:

1.帶參構造,創建可執行對象

2.創建空線程對象,移動構造或者移動賦值,把右值線程對象轉移過去?

帶參構造

第二種方式是直接在創建線程的時候就給該線程關聯上要執行的函數

#include <iostream>
#include <thread>
using namespace std;void func(int x)
{cout << this_thread::get_id() << endl;for(int i = 1; i <= x; i++)cout << "music" << endl;
}
int main()
{// 2.帶參構造thread t2(func, 9);// 線程等待t2.join();return 0;
}

移動構造

第三種方式是使用移動構造

#include <iostream>
#include <thread>
using namespace std;void func(int x)
{cout << this_thread::get_id() << endl;for(int i = 1; i <= x; i++)cout << "music" << endl;
}
int main()
{// 3.移動構造thread t3 = thread(func, 9);// 線程等待t3.join();return 0;
}

ref

如果我們現在要使用兩個線程對一個局部變量進行++操作,那么我們應該如何寫代碼呢?我們肯定是創建兩個線程,然后把該局部變量以引用的方式傳遞過去,這樣線程執行的函數就會修改我們傳遞的局部變量,這里當然是有線程安全問題需要加鎖,但我們先不考慮加鎖,我們看下面的代碼。

#include <iostream>
#include <thread>
using namespace std;void add(int& x)
{for (int i = 0; i < 100; i++){x++;}
}int main()
{int num = 0;// 錯誤寫法thread t1(add, num);// 這樣寫會報錯,看似是引用的方式接收,但實際上接收的是num的拷貝// 正確寫法thread t1(add, ref(num)); // 如果是引用方式接收,需要使用ref轉一下,這樣寫才正確// 錯誤寫法thread t2(add, num);// 這樣寫會報錯,看似是引用的方式接收,但實際上接收的是num的拷貝// 正確寫法thread t2(add, ref(num));// 如果是引用方式接收,需要使用ref轉一下,這樣寫才正確this_thread::sleep_for(chrono::seconds(2));cout << num << endl;t1.join();t2.join();return 0;
}

上面的代碼看上去是正確的,但是實際我們編譯的時候發現編譯不過去,原因在于add的參數是引用的方式接收的,但是由于num是先傳給了t1的構造函數,然后在給add傳遞過去,相當于不是直接傳過去,而是中間轉了一層,所以這里看似是傳遞的num但實際傳遞的是num的拷貝,因此如果要傳遞num的引用,需要加上ref()。

休眠可以使用this_thread::sleep_for這個函數,chrono是時鐘的意思,seconds是按秒休眠,也可以按毫秒milliseconds休眠。

mutex

這里毫無疑問是有線程安全的問題的,因此是需要加鎖的。

要使用鎖需要包含頭文件,#include <mutex>

然后直接就可以使用mutex定義一把鎖,如果要加鎖調用lock方法,解鎖調用unlock方法即可。

此時的代碼就可以這樣更改。

void add(mutex& mtx, int& x)
{for (int i = 0; i < 100; i++){mtx.lock();x++;mtx.unlock();}
}int main()
{int num = 0;mutex mt; //定義一把鎖thread t1(add, ref(mt), ref(num));thread t2(add, ref(mt), ref(num));this_thread::sleep_for(chrono::seconds(2));cout << num << endl;t1.join();t2.join();return 0;
}

unique_lock和lock_guard

但是有的時候我們可能加鎖的時候忘記解鎖了,就會導致死鎖,那么我們可以使用智能指針把鎖管理起來,創建鎖的時候直接加鎖,出了作用域變量銷毀在析構的時候直接解鎖,此時我們就不需要手動的加鎖解鎖了,那么我們要自己實現嗎?C++已經給我們提供了對鎖進行管理的類,unique_lock和lock_guard,我們直接使用即可。

我們先來看unique_lock。

unique_lock是一個模板類,是專門用來管理所的類,類型直接傳遞鎖的類型mutex即可,然后構造的時候把鎖傳遞過去即可,一下是他的一些構造函數。

因此我們在線程函數里可以使用unique_lock,對傳遞過來的鎖進行管理。

void add(mutex& mtx, int& x)
{for (int i = 0; i < 100; i++){unique_lock<mutex> lock(mtx);x++;}
}

lock_guard和unique_lock的使用方法是一樣的。

void add(mutex& mtx, int& x)
{for (int i = 0; i < 100; i++){//unique_lock<mutex> lock(mtx);lock_guard<mutex> lock(mtx);x++;}
}

lock_guard相比unique_lock來說更輕量一些,unique_lock則更加靈活,可以支持延遲鎖定、嘗試鎖定等,但開銷會比較大。這兩個都是基于RAII的,頭文件都是<mutex>,它們兩個可以用來配合條件變量。

atomic

我們加鎖是因為對一個數++操作不是原子的,C++給我們們提供了atomic這個類,我們可以直接使用這個類定義變量,定義的變量++和--操作是原子的操作,此時就可以不需要加鎖。

使用非常簡單,atomic是一個類,如果要定義一個int類型的變量,直接模版參數傳遞int即可,比如說atomic<int> nums, 此時對nums++和--就是原子的操作,也可以對他進行打印和賦值,使用起來和int類型是一樣的。

void add(mutex& mtx, atomic<int>& x)
{for (int i = 0; i < 100; i++){x++;}
}int main()
{atomic<int> num = 0;mutex mt;thread t1(add, ref(mt), ref(num));thread t2(add, ref(mt), ref(num));this_thread::sleep_for(chrono::seconds(2));cout << num << endl;t1.join();t2.join();return 0;
}

條件變量

頭文件:#include <condition_variable>

要使用條件變量需要包含上面的頭文件,創建條件變量直接使用condition_variable創建一個變量即可。

wait方法:將該線程加入到阻塞隊列中

我們可以直接調用wait就可以將線程加入到阻塞隊列,傳遞的參數是一個RAII的鎖,也就是unique_lock管理的鎖,加入到阻塞隊列后鎖會被釋放。

?喚醒線程:將在阻塞隊列的函數喚醒

喚醒線程有兩個,一個是喚醒阻塞隊列中的全部線程,一個是喚醒阻塞隊列中的一個線程。這兩個喚醒函數都不需要傳遞參數。

接下來我們用條件變量來實現一個兩個線程交替打印奇偶數

兩個線程交替打印奇偶數

首先要創建兩個線程,當然也需要創建鎖和條件變量,也需要一個變量number,number初始化為1,我們規定,讓線程1先來打印number,線程1打印完畢后對number進行+1操作,此時number就變成了偶數,然后讓線程2打印number,此時打印的就是偶數,然后+1,讓線程1打印,如此交替執行即可。

既然要讓線程1先打印,那么線程2一定要被阻塞住,否則是無法保證打印順序的。一開始線程1打印,然后線程2被阻塞,線程1打印完畢后要喚醒線程2去打印,然后把自己阻塞住,線程2打印完畢后喚醒線程1,然后讓自己阻塞,這樣就可以控制打印順序。

那么我們如何控制誰先打印和阻塞?我們可以使用一個flag標記。

剛開始flag是假,線程1的判斷條件是while(flag),因此,一開始線程1不會被阻塞會直接打印,而線程2條件是while(!flag),會直接進入阻塞隊列阻塞,當線程1打印完后,把flag置為true,然后喚醒線程2,線程2的判斷條件不成立不會進入阻塞隊列會打印線程1,然而線程1條件成立會進入阻塞隊列,線程2打印完畢后把flag置為false,自己會進入阻塞隊列,而線程1條件不成立會直接打印,此時就完成了打印順序的控制。

當然我們也可以不需要加鎖,用atomic創建變量num即可。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>using namespace std;
int main()
{int num = 1;bool flag = false;mutex mtx; // 鎖condition_variable cond; // 條件變量thread t1([&]() {for (int i = 0; i < 50; ++i){unique_lock<mutex> lock(mtx);while (flag)cond.wait(lock);cout << "線程1:" << num << endl;num++;flag = true;cond.notify_one();}});thread t2([&]() {for (int i = 0; i < 50; i++){unique_lock<mutex> lock(mtx);while (!flag) cond.wait(lock);cout <<  "線程2:" << num << endl;num++;flag = false;cond.notify_one();}//std::this_thread::sleep_for(std::chrono::seconds(1));});t1.join();t2.join();return 0;
}

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

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

相關文章

梯度提升用于高效的分類與回歸

人工智能例子匯總:AI常見的算法和例子-CSDN博客 使用 決策樹(Decision Tree) 實現 梯度提升(Gradient Boosting) 主要是模擬 GBDT(Gradient Boosting Decision Trees) 的原理,即: 第一棵樹擬合原始數據計算殘差(負梯度方向)用新的樹去擬合殘差累加所有樹的預測值重…

Golang 并發機制-3:通道(channels)機制詳解

并發編程是一種創建性能優化且響應迅速的軟件的強大方法。Golang&#xff08;也稱為 Go&#xff09;通過通道&#xff08;channels&#xff09;這一特性&#xff0c;能夠可靠且優雅地實現并發通信。本文將揭示通道的概念&#xff0c;解釋其在并發編程中的作用&#xff0c;并提供…

Unity開發游戲使用XLua的基礎

Unity使用Xlua的常用編碼方式&#xff0c;做一下記錄 1、C#調用lua 1、Lua解析器 private LuaEnv env new LuaEnv();//保持它的唯一性void Start(){env.DoString("print(你好lua)");//env.DoString("require(Main)"); 默認在resources文件夾下面//幫助…

筆記:使用ST-LINK燒錄STM32程序怎么樣最方便?

一般板子在插件上&#xff0c; 8腳 3.3V;9腳 CLK;10腳 DIO;4腳GND ST_Link 19腳 3.3V;9腳 CLK;7腳 DIO;20腳 GND 燒錄軟件&#xff1a;ST-LINK Utility&#xff0c;Keil_5; ST_Link 接口針腳定義&#xff1a; 按定義連接ST_Link與電路板&#xff1b; 打開STM32 ST-LINK Uti…

網絡測試工具

工具介紹&#xff1a; 這是一個功能完整的網絡測速工具&#xff0c;可以測試網絡的下載速度、上傳速度和延遲。 功能特點&#xff1a; 1. 速度測試 - 下載速度測試 - 上傳速度測試 - Ping延遲測試 - 自動選擇最佳服務器 2. 實時顯示 - 進度條顯示測試進度 - 實時顯示測試狀…

java每日精進1.31(SpringSecurity)

在所有的開發的系統中&#xff0c;都必須做認證(authentication)和授權(authorization)&#xff0c;以保證系統的安全性。 一、基礎使用 1.依賴 <dependencies><!-- 實現對 Spring MVC 的自動化配置 --><dependency><groupId>org.springframework.bo…

簡單的SQL語句的快速復習

語法的執行順序 select 4 字段列表 from 1 表名列表 where 2 條件列表 group by 3 分組前過濾 having 分組后過濾 order by 5 排序字段列表 limit 6 分頁參數 聚合函數 count 統計數量 max 最大值 min 最小值 avg 平均 sum 總和 分組查詢使…

《程序人生》工作2年感悟

一些雜七雜八的感悟&#xff1a; 1.把事做好比什么都重要&#xff0c; 先樹立量良好的形象&#xff0c;再橫向發展。 2.職場就是人情世故&#xff0c;但也不要被人情世故綁架。 3.要常懷感恩的心&#xff0c;要記住幫助過你的人&#xff0c;愿意和你分享的人&#xff0c;有能力…

17.2 圖形繪制8

版權聲明&#xff1a;本文為博主原創文章&#xff0c;轉載請在顯著位置標明本文出處以及作者網名&#xff0c;未經作者允許不得用于商業目的。 17.2.10 重繪 先看以下例子&#xff1a; 【例 17.28】【項目&#xff1a;code17-028】繪制填充矩形。 private void button1_Clic…

自定義數據集 使用pytorch框架實現邏輯回歸并保存模型,然后保存模型后再加載模型進行預測,對預測結果計算精確度和召回率及F1分數

import numpy as np import torch import torch.nn as nn import torch.optim as optim from sklearn.metrics import precision_score, recall_score, f1_score# 數據準備 class1_points np.array([[1.9, 1.2],[1.5, 2.1],[1.9, 0.5],[1.5, 0.9],[0.9, 1.2],[1.1, 1.7],[1.4,…

neo4j入門

文章目錄 neo4j版本說明部署安裝Mac部署docker部署 neo4j web工具使用數據結構圖數據庫VS關系數據庫 neo4j neo4j官網Neo4j是用ava實現的開源NoSQL圖數據庫。Neo4作為圖數據庫中的代表產品&#xff0c;已經在眾多的行業項目中進行了應用&#xff0c;如&#xff1a;網絡管理&am…

腳本運行禁止:npm 無法加載文件,因為在此系統上禁止運行腳本

問題與處理策略 1、問題描述 npm install -D tailwindcss執行上述指令&#xff0c;報如下錯誤 npm : 無法加載文件 D:\nodejs\npm.ps1&#xff0c;因為在此系統上禁止運行腳本。 有關詳細信息&#xff0c;請參閱 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_…

Java基礎——分層解耦——IOC和DI入門

目錄 三層架構 Controller Service Dao ?編輯 調用過程 面向接口編程 分層解耦 耦合 內聚 軟件設計原則 控制反轉 依賴注入 Bean對象 如何將類產生的對象交給IOC容器管理&#xff1f; 容器怎樣才能提供依賴的bean對象呢&#xff1f; 三層架構 Controller 控制…

智慧園區系統集成解決方案引領未來城市管理的智能化轉型

內容概要 在現代城市管理的背景下&#xff0c;“智慧園區系統集成解決方案”正扮演著越來越重要的角色。這種解決方案不僅僅是技術上的創新&#xff0c;更是一種全新的管理理念&#xff0c;它旨在通過高效的數據整合與分析&#xff0c;優化資源配置&#xff0c;提升運營效率。…

99.24 金融難點通俗解釋:MLF(中期借貸便利)vs LPR(貸款市場報價利率)

目錄 0. 承前1. 什么是MLF&#xff1f;1.1 專業解釋1.2 通俗解釋1.3 MLF的三個關鍵點&#xff1a; 2. 什么是LPR&#xff1f;2.1 專業解釋2.2 通俗解釋2.3 LPR的三個關鍵點&#xff1a; 3. MLF和LPR的關系4. 傳導機制4.1 第一步&#xff1a;央行調整MLF4.2 第二步&#xff1a;銀…

【VM】VirtualBox安裝CentOS8虛擬機

閱讀本文前&#xff0c;請先根據 VirtualBox軟件安裝教程 安裝VirtualBox虛擬機軟件。 1. 下載centos8系統iso鏡像 可以去兩個地方下載&#xff0c;推薦跟隨本文的操作用阿里云的鏡像 centos官網&#xff1a;https://www.centos.org/download/阿里云鏡像&#xff1a;http://…

Elasticsearch中的度量聚合:深度解析與實戰應用

在大數據和實時分析日益重要的今天&#xff0c;Elasticsearch以其強大的搜索和聚合能力&#xff0c;成為了眾多企業和開發者進行數據分析和處理的首選工具。本文將深入探討Elasticsearch中的度量聚合&#xff08;Metric Aggregations&#xff09;&#xff0c;展示其如何在數據分…

C_C++輸入輸出(下)

C_C輸入輸出&#xff08;下&#xff09; 用兩次循環的問題&#xff1a; 1.一次循環決定打印幾行&#xff0c;一次循環決定打印幾項 cin是>> cout是<< 字典序是根據字符在字母表中的順序來比較和排列字符串的&#xff08;字典序的大小就是字符串的大小&#xff09;…

電腦要使用cuda需要進行什么配置

在電腦上使用CUDA&#xff08;NVIDIA的并行計算平臺和API&#xff09;&#xff0c;需要進行以下配置和準備&#xff1a; 1. 檢查NVIDIA顯卡支持 確保你的電腦擁有支持CUDA的NVIDIA顯卡。 可以在NVIDIA官方CUDA支持顯卡列表中查看顯卡型號是否支持CUDA。 2. 安裝NVIDIA顯卡驅動…

深入解析:一個簡單的浮動布局 HTML 示例

深入解析&#xff1a;一個簡單的浮動布局 HTML 示例 示例代碼解析代碼結構分析1. HTML 結構2. CSS 樣式 核心功能解析1. 浮動布局&#xff08;Float&#xff09;2. 清除浮動&#xff08;Clear&#xff09;3. 其他樣式 效果展示代碼優化與擴展總結 在網頁設計中&#xff0c;浮動…