《C++11:通過thread類編寫C++多線程程序》

關于多線程的概念與理解,可以先了解Linux下的底層線程。當對底層線程有了一定程度理解以后,再學習語言級別的多線程編程就輕而易舉了。

【Linux】多線程 -> 從線程概念到線程控制

【Linux】多線程 -> 線程互斥與死鎖


語言級別的多線程編程最大的好處就是可以跨平臺,使用語言級別編寫的多線程程序不僅可以在Windows下直接編譯運行,在Linux下也是可以直接編譯運行的。其實本質還是調用了不同操作系統的底層線程API。

多線程簡單使用

  • 怎么創建啟動一個線程?

#include<iostream>
#include<thread>
#include<chrono>void threadHandle1(int time)
{// 讓線程休眠兩秒std::this_thread::sleep_for(std::chrono::seconds(time));std::cout << "hello thread1" << std::endl;
}int main()
{// 創建一個線程對象,傳入線程函數以及線程函數所需要的參數,線程就開始執行了std::thread t1(threadHandle1, 2);// 主線程等待子線程結束,再繼續往下運行t1.join();std::cout << "main thread done!" << std::endl;return 0;
}

主線程要t1.join()等待子線程結束之后才能向后運行。如果不等待,就會異常終止。

  • 子線程如何結束?

子線程函數運行完成就結束了。

也可以將線程設置為分離狀態,主線程就可以不用等待子進程執行完再繼續向后運行了。

void threadHandle1(int time)
{// 讓線程休眠兩秒std::this_thread::sleep_for(std::chrono::seconds(time));std::cout << "hello thread1" << std::endl;
}int main()
{// 創建一個線程對象,傳入線程函數以及線程函數所需要的參數,線程就開始執行了std::thread t1(threadHandle1, 2);// 主線程等待子線程結束,再繼續往下運行// t1.join();// 設置線程為分離狀態t1.detach();std::cout << "main thread done!" << std::endl;return 0;
}

  • ?主線程如何處理子線程?

1、t1.join()等待線程,等待線程結束后主線程再繼續向后運行。

2、t1.detach()分離線程,主線程結束,整個進程就結束了,所有線程都自動結束了。


#include<iostream>
#include<thread>
#include<chrono>void threadHandle1(int time)
{// 讓線程休眠time秒std::this_thread::sleep_for(std::chrono::seconds(time));std::cout << "hello thread1" << std::endl;
}void threadHandle2(int time)
{// 讓線程休眠time秒std::this_thread::sleep_for(std::chrono::seconds(time));std::cout << "hello thread1" << std::endl;
}int main()
{// 創建一個線程對象,傳入線程函數以及線程函數所需要的參數,線程就開始執行了std::thread t1(threadHandle1, 2);std::thread t2(threadHandle2, 2);// 主線程等待子線程結束,再繼續往下運行t1.join();t2.join();// 設置線程為分離狀態// t1.detach();std::cout << "main thread done!" << std::endl;return 0;
}

?mutex互斥鎖和lock_guard

#include<iostream>
#include<thread>
#include<list>int tickets = 1000;void getTicket(int i)
{// 模擬用戶搶票的行為while (tickets > 0){std::cout << "用戶:" << i << "正在進行搶票!" << tickets << std::endl;tickets--;std::this_thread::sleep_for(std::chrono::microseconds(100));}
}int main()
{std::list<std::thread> list;for (int i = 1; i <= 3; i++){list.push_back(std::thread(getTicket, i));}for (std::thread &t: list){t.join();}return 0;
}

輸出的結果雜亂無章,并且搶票出現負數。

多線程對臨界區的訪問會存在競態條件:臨界區代碼段在多線程環境下執行,隨著線程的調度時許不同,從而產生不同的結果。所以,我們要保證對臨界區的原子操作,是通過對臨界區加鎖完成的。

由于多個線程同時向標準輸出流std::cout輸出信息,會造成輸出混亂,各線程的輸出可能相互穿插。

在多線程環境下,多個線程同時訪問和修改共享資源tickets,會引發數據競爭問題。比如,當一個線程檢查到tickets>0后,在執行tickets--操作之前,另一個線程也可能檢查到tickets>0,進而導致重復售票或者出現負數票數的情況。

++/--操作并不是原子性的,其實是對應三條匯編指令完成的。

  • 讀取:從內存中把變量的值讀取到寄存器
  • 修改:在寄存器里將變量的值+1/-1
  • 寫入:把修改后的值寫入到內存

在單線程環境下,這三個步驟順序執行不會有問題。但是在多線程環境下,多個線程可能對同一個變量同時進行++/--操作,從而導致數據競爭的問題。

可以看下面的過程演示。

一:

二:

三:

C++11是通過加鎖來保證++/--操作的原子性的。


#include<iostream>
#include<thread>
#include<list>
#include<mutex>int tickets = 1000;
std::mutex mtx; // 全局的一把鎖void getTicket(int i)
{// 模擬用戶搶票的行為while (tickets > 0){mtx.lock();// 鎖+雙重判斷,如果不雙重判斷,當tickets為1時,用戶正在搶票還沒有執行到tickets--,其他用戶判斷tickets>0,也進來了等待鎖。// 用戶搶票之后,其他用戶獲取到鎖還會進行搶票。if (tickets > 0){// 臨界區代碼段 - 加鎖保護std::cout << "用戶:" << i << "正在進行搶票!" << tickets << std::endl;tickets--;mtx.unlock();}}
}int main()
{std::list<std::thread> list;for (int i = 1; i <= 3; i++){list.push_back(std::thread(getTicket, i));}for (std::thread &t: list){t.join();}return 0;
}

通過加鎖和雙重判斷的方式,這樣就能做到對多線程對共享資源tickets的安全訪問了。

lock_guard和unique_lock

其實不需要我們手動加鎖和解鎖,因為,如果臨界區內有return,break等語句,此線程獲取鎖,但是在釋放鎖之前break或者renturn了,導致鎖沒有釋放。那么其他線程也獲取不了鎖,就會造成死鎖問題,所以對于這把互斥鎖要有RAII的思想。

使用lock_guard。構造函數獲取鎖,析構函數釋放鎖。

int tickets = 1000;
std::mutex mtx; // 全局的一把鎖void getTicket(int i)
{// 模擬用戶搶票的行為while (tickets > 0){{std::lock_guard<std::mutex> lock(mtx);// mtx.lock();// 鎖+雙重判斷,如果不雙重判斷,當tickets為1時,用戶正在搶票還沒有執行到tickets--,其他用戶判斷tickets>0,也進來了等待鎖。// 用戶搶票之后,其他用戶獲取到鎖還會進行搶票。if (tickets > 0){std::cout << "用戶:" << i << "正在進行搶票!" << tickets << std::endl;tickets--;}// mtx.unlock();}std::this_thread::sleep_for(std::chrono::microseconds(100));}
}

不管臨界區是正常執行,還是break或return了,出了作用域,lock_guard會自動調用析構函數釋放鎖,保證所有線程都能釋放鎖,避免死鎖問題發生。

lock_guard不支持拷貝構造和賦值運算符重載,如果需要拷貝和賦值可以使用unique_lock,支持移動語義,可以使用右值引用的拷貝構造和賦值運算符重載。

int tickets = 1000;
std::mutex mtx; // 全局的一把鎖void getTicket(int i)
{// 模擬用戶搶票的行為while (tickets > 0){{std::unique_lock<std::mutex> lck(mtx);// std::lock_guard<std::mutex> lock(mtx);// mtx.lock();// 鎖+雙重判斷,如果不雙重判斷,當tickets為1時,用戶正在搶票還沒有執行到tickets--,其他用戶判斷tickets>0,也進來了等待鎖。// 用戶搶票之后,其他用戶獲取到鎖還會進行搶票。if (tickets > 0){std::cout << "用戶:" << i << "正在進行搶票!" << tickets << std::endl;tickets--;}// mtx.unlock();}std::this_thread::sleep_for(std::chrono::microseconds(100));}
}

總結:lock_guard不能用在函數傳遞或返回的過程中,因為lock_guard刪除了拷貝構造和賦值,只能用在簡單的臨界區代碼段的互斥操作中。unique_guard可以用在函數傳遞或返回的過程中,因為支持移動語義,可以使用移動構造和移動賦值。unique_guard通常與條件變量一起使用。

線程間的同步通信

生產者-消費者模型

std::mutex mtx;
std::condition_variable cv;// 邊生產邊消費
class Queue
{
public:void put(int val){// 加鎖std::unique_lock<std::mutex> lock(mtx);// 如果隊列不為空,則等待while (!que.empty()){// 1.進入等待狀態 2.釋放鎖cv.wait(lock);// 被喚醒之后,等待狀態進入阻塞狀態,獲取鎖進入就緒狀態繼續執行}que.push(val);cv.notify_all(); // 通知消費者消費std::cout << "生產者 生產:" << val << "號物品" << std::endl;}int get(){// 加鎖std::unique_lock<std::mutex> lock(mtx);// 如果隊列為空,則等待while (que.empty()){// 1.進入等待狀態 2.釋放鎖cv.wait(lock);// 被喚醒之后,等待狀態進入阻塞狀態,獲取鎖進入就緒狀態繼續執行}int val = que.front();que.pop();cv.notify_all(); // 通知生產者生產std::cout << "消費者 消費:" << val << "號物品" << std::endl;return val;}
private:std::queue<int> que;
};// 生產
void producer(Queue* que)
{for (int i = 0; i <= 10; i++){que->put(i);std::this_thread::sleep_for(std::chrono::microseconds(100));}
}
// 消費
void conducer(Queue* que)
{for (int i = 0; i <= 10; i++){que->get();std::this_thread::sleep_for(std::chrono::microseconds(100));}
}
int main()
{Queue que;std::thread p(producer, &que);std::thread c(conducer, &que);p.join();c.join();return 0;
}

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

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

相關文章

c++位運算總結

在C中&#xff0c;位運算是對二進制位進行操作的運算&#xff0c;主要有以下幾種&#xff1a; 1. 按位與&#xff08; & &#xff09;&#xff1a;兩個操作數對應位都為1時&#xff0c;結果位才為1&#xff0c;否則為0。例如 3 & 5 &#xff0c; 3 二進制是 0000 0011…

1.1 計算機網絡的概念

首先來看什么是計算機網絡&#xff0c;關于計算機網絡的定義并沒有一個統一的標準&#xff0c;不同的教材有 不同的說法&#xff08;這是王道書對于計算機網絡的定義&#xff09;&#xff0c;我們可以結合自己的生活經驗去體會這個 定義。 可以用不同類型的設備去連接計算機網絡…

用LLama factory時報類似Process 2504721 got signal: 1的解決方法

之前用nohup來遠程跑LLama factory微調腳本&#xff0c;是沒有問題的&#xff0c;但今天發現運行類似下面這個命令時&#xff0c; nohup llamafactory-cli train examples/train_qlora/qwen_lora.yaml 只要一關閉ssh session&#xff0c;就會終止訓練&#xff0c;報類似&…

python常用內置時間函數+藍橋杯時間真題

1.time 1.1 time.time() 時間戳指&#xff1a;1970年1月1日開始到現在所經過的秒數 import time print(time.time()) # 輸出可得1970年1月1日開始到執行此代碼所經過的秒數 1.2 time.localtime() 返回一個當前時間的時間對象&#xff0c;具體信息&#xff0c;并且可以單獨…

一個用 C 語言打印出所有三位數水仙花數的程序

水仙花數&#xff08;Narcissistic number&#xff09;是指一個三位數&#xff0c;其各位數字的立方和等于該數本身。例如&#xff1a;153 是一個水仙花數&#xff0c;因為 (1^3 5^3 3^3 153)。 以下是一個用 C 語言打印出所有三位數水仙花數的程序&#xff1a; 代碼實現 …

利用 VSCode 配置提升 vibe coding 開發效率

利用 VSCode 配置提升 vibe coding 開發效率 Vibe Coding&#xff08;氛圍編程&#xff09;是一種基于AI的編程方法&#xff0c;其核心在于通過自然語言描述軟件需求&#xff0c;再由大規模語言模型&#xff08;LLM&#xff09;自動生成代碼&#xff0c;從而實現對傳統手寫編程…

練習題:110

目錄 Python題目 題目 題目分析 需求理解 關鍵知識點 實現思路分析 代碼實現 代碼解釋 函數定義&#xff1a; 計算值的總和&#xff1a; 測試函數&#xff1a; 運行思路 結束語 Python題目 題目 定義一個函數&#xff0c;接受一個字典作為參數&#xff0c;返回字…

處理 Linux 信號:進程控制與異常管理的核心

個人主頁&#xff1a;chian-ocean 文章專欄-Linux 前言&#xff1a; 在 Linux 操作系統中&#xff0c;信號是用于進程間通信的一種機制&#xff0c;能夠向進程發送通知&#xff0c;指示某些事件的發生。信號通常由操作系統內核、硬件中斷或其他進程發送。接收和處理信號是 Li…

通信協議之串口

文章目錄 簡介電平標準串口參數及時序USART與UART過程引腳配置 簡介 點對點&#xff0c;只能兩設備通信只需單向的數據傳輸時&#xff0c;可以只接一根通信線當電平標準不一致時&#xff0c;需要加電平轉換芯片&#xff08;一般從控制器出來的是信號是TTL電平&#xff09;地位…

Unity編輯器功能及拓展(1) —特殊的Editor文件夾

Unity中的Editor文件夾是一個具有特殊用途的目錄&#xff0c;主要用于存放與編輯器擴展功能相關的腳本和資源。 一.糾纏不清的UnityEditor 我們Unity中進行游戲構建時&#xff0c;我們經常遇到關于UnityEditor相關命名空間丟失的報錯&#xff0c;這時候&#xff0c;只得將報錯…

工具類-csv文件導入數據庫思路

首先&#xff0c;讓我們來看下數據庫建表語句&#xff1a; CREATE TABLE behavior_reports (id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 報告ID,report_type VARCHAR(50) NOT NULL COMMENT 報告類型(daily, weekly, monthly),start_date DATE NOT NULL COMMENT 開始日期,e…

軟件工程之軟件開發模型(瀑布、迭代、敏捷、DevOps)

1. 瀑布模型&#xff08;Waterfall Model&#xff09; 定義與流程 瀑布模型是線性順序的開發流程&#xff0c;包含需求分析、設計、編碼、測試、維護等階段&#xff0c;每個階段完成后才能進入下一階段&#xff0c;類似“瀑布流水”逐級推進。 核心特點 嚴格階段劃分&#…

FreeRTOS與RT-Thread內存分配對比分析

一、動態內存分配策略 ?FreeRTOS ?分配算法多樣性&#xff1a;提供5種動態內存管理算法&#xff08;heap_1至heap_5&#xff09;&#xff0c;覆蓋從簡單到復雜的場景。例如&#xff1a; heap_1&#xff1a;僅支持分配不支持釋放&#xff0c;適用于固定任務棧分配。heap_4&…

202519 | Mybatis-Plus

快速入門 MyBatis-Plus&#xff08;簡稱 MP&#xff09;是 MyBatis 的增強工具&#xff0c;它在 MyBatis 的基礎上只做增強不做改變&#xff0c;簡化了開發&#xff0c;提高了效率。以下是 MyBatis-Plus 的快速入門指南&#xff0c;幫助您快速上手使用。 1. 環境準備 JDK&…

Linux C語言調用第三方庫,第三方庫如何編譯安裝

在 Linux 環境下使用 C 語言調用第三方庫時&#xff0c;通常需要先對第三方庫進行編譯和安裝。以下為你詳細介紹一般的編譯安裝步驟&#xff0c;并給出不同類型第三方庫&#xff08;如使用 Makefile、CMake 構建系統&#xff09;的具體示例。 一般步驟 1. 獲取第三方庫源碼 …

linux基本命令(1)--linux下的打包命令 -- tar 和gzip

tar 解壓 &#xff0c;打包 語法&#xff1a;tar [主選項輔選項] 文件或者目錄 使用該命令時&#xff0c;主選項是必須要有的&#xff0c;它告訴tar要做什么事情&#xff0c;輔選項是輔助使用的&#xff0c;可以選用。 主選項&#xff1a; c 創建新的檔案文件。如果用戶想備…

Python 序列構成的數組(對序列使用+和_)

對序列使用和* Python 程序員會默認序列是支持 和 * 操作的。通常 號兩側的序列由 相同類型的數據所構成&#xff0c;在拼接的過程中&#xff0c;兩個被操作的序列都不會被 修改&#xff0c;Python 會新建一個包含同樣類型數據的序列來作為拼接的結果。 如果想要把一個序列…

[ C語言 ] | 從0到1?

目錄 認識計算機語言 C語言 工欲善其事必先利其器 第一個C語言代碼 這一些列 [ C語言 ] &#xff0c;就來分享一下 C語言 相關的知識點~ 認識計算機語言 我們說到計算機語言&#xff0c;語言&#xff0c;就是用來溝通的工具&#xff0c;計算機語言呢&#xff1f;就是我們…

【通道注意力機制】【SENet】Squeeze-and-Excitation Networks

0.論文摘要 卷積神經網絡建立在卷積操作的基礎上&#xff0c;通過融合局部感受野內的空間和通道信息來提取有意義的特征。為了增強網絡的表示能力&#xff0c;最近的一些方法展示了增強空間編碼的好處。在本研究中&#xff0c;我們專注于通道關系&#xff0c;并提出了一種新穎…

kubernetes Calico(CNI) NetworkPolicy 流量管理 設置networkpolicy 策略 下集

1、kubernetes 網絡策略&#xff08;網絡隔離策略&#xff09; Network Policy 是 Kubernetes 中用于控制 Pod 之間網絡通信的一種機制。它通過定義規則&#xff0c;限制哪些 Pod 或外部實體可以與目標 Pod 通信&#xff08;基于標簽、命名空間、端口等&#xff09;。Network …