《C++并发编程》《线程管理》

文章目录

    • 一、线程的基本管控
      • 1.1发起线程
      • 1.2等待线程完成
      • 1.3出现异常情况下等待
      • 1.4后台运行线程
    • 二、向线程函数传递参数
    • 三、转移线程归属权
    • 四、运行时选择线程数量
    • 五、识别线程
    • 六、总结
    • 七、参考书籍

一、线程的基本管控

1.1发起线程

线程通过构建std::thread对象而启动,该对象指明线程要运行的任务。
举一个简单的栗子:

#include <iostream>  
#include <thread>  // 线程函数  
void threadFunction() {  for (int i = 0; i < 10; ++i) {  std::cout << "Thread function executing..." << std::endl;  }  
}  
int main() {  // 创建线程并启动  std::thread t(threadFunction);   // 等待线程完成  t.join();  return 0;  
}

1.2等待线程完成

若需要等待线程完成,那么可以在与之关联的std::thread实例上,通过调用成员函数join()实现。除了join(),还可以对线程进行判断joinable()。
举一个简单的栗子:

#include <iostream>  
#include <thread>  void threadFunction() {  std::cout << "Thread function executing..." << std::endl;  
}  int main() {  // 创建线程并启动  std::thread t(threadFunction);  // 检查线程是否可joinable  if (t.joinable()) {  // 等待线程完成  t.join();  } else {  std::cout << "Thread is not joinable." << std::endl;  }  return 0;  
}

1.3出现异常情况下等待

当然程序在运行时,可能会出一些意外情况。为了防止因抛出异常而导致的应用程序终结,我们需要决定如何处理这种情况。
举一个简单的栗子:

#include <iostream>  
#include <thread>  
#include <mutex>  
#include <condition_variable>  std::mutex mtx;  
std::condition_variable cv;  
bool ready = false;  void workerThread() {  try {  // 模拟线程执行过程中可能会出现的问题  std::this_thread::sleep_for(std::chrono::seconds(1));  mtx.lock();  ready = true;  cv.notify_one();  mtx.unlock();  } catch (const std::exception& e) {  std::cout << "Thread caught exception: " << e.what() << std::endl;  }  
}  int main() {  std::thread t(workerThread);  t.detach(); // 分离线程,使其在后台运行  // 主线程等待子线程完成  std::unique_lock<std::mutex> lock(mtx);  cv.wait(lock, []{ return ready; });  std::cout << "Thread finished." << std::endl;  return 0;  
}

1.4后台运行线程

std::thread::detach 是 C++11 标准库中 头文件提供的一个函数,用于将线程对象与其底层线程分离。一旦线程被分离,当它结束时,其资源会自动被回收,而不需要通过 std::thread::join 进行显式等待。
当调用 detach 之后,该 std::thread 对象将不再代表一个活动线程,并且无法再被 join。如果尝试对已分离的线程调用 join,程序会抛出一个 std::system_error 异常。
举个简单的栗子:

#include <iostream>  
#include <thread>  
#include <chrono>  void threadFunction() {  for (int i = 0; i < 5; ++i) {  std::this_thread::sleep_for(std::chrono::seconds(1));  std::cout << "Thread function executing..." << std::endl;  }  
}  int main() {  // 创建线程并启动  std::thread t(threadFunction);  // 分离线程,使其在后台运行  t.detach();  // 主线程继续执行其他任务  for (int i = 0; i < 5; ++i) {  std::this_thread::sleep_for(std::chrono::seconds(1));  std::cout << "Main thread executing..." << std::endl;  }  return 0;  
}

detach函数使用注意事项:

  • 线程资源管理:detach函数将线程与主线程分离,这意味着主线程不再负责等待该线程的完成。一旦线程被分离,它的资源将由运行时库负责清理。因此,如果主线程在后台线程完成之前结束,那么后台线程可能会变成“僵尸线程”,这可能导致资源泄漏或其他问题。
  • 线程安全:使用detach时,需要注意线程安全。如果多个线程同时操作同一个数据结构,而其中一个线程调用了detach,那么这个数据结构可能会在未同步的情况下被修改,导致数据不一致或其他问题。
  • 异常处理:如果在线程函数中抛出了异常,而该异常未被捕获和处理,那么当线程结束时,该异常可能会被抛出并导致程序崩溃。因此,在使用detach时,需要确保线程函数中的异常被正确处理。
  • joinable检查:在调用detach之前,最好先检查线程是否可以被join。如果线程已经被join或detach过,再次调用detach会导致未定义的行为。

二、向线程函数传递参数

若要向新线程上的函数或可调用对象传递参数,方法相当简单,直接向std::thread的构造函数添加更多参数即可。
举个简单的栗子:

#include <iostream>  
#include <thread>  // 这是线程函数,接受一个整数参数  
void threadFunction(int param) {  std::cout << "Thread function received: " << param << std::endl;  
}  int main() {  // 创建一个线程,并传递参数  std::thread t(threadFunction, 42);  // 等待线程完成  t.join();  return 0;  
}

向线程传递参数的注意点:
向线程函数传递参数时,需要注意以下几点:

  • 参数类型匹配:确保传递给线程函数的参数类型与函数期望的参数类型匹配。否则,可能会导致编译错误或运行时错误。
  • 参数的拷贝语义:在传递参数时,要注意参数的拷贝语义。如果传递的是大型对象或可变对象,可能会导致不必要的内存分配和拷贝。可以使用引用或指针来避免不必要的拷贝。
  • 线程安全:如果多个线程同时访问和修改共享数据,需要确保线程安全。可以使用互斥锁、条件变量等同步机制来保护共享数据。
  • 异常处理:如果线程函数可能抛出异常,需要确保异常能够被正确处理。可以使用try-catch语句块来捕获并处理异常。
  • 参数的传递方式:可以使用值传递、引用传递或指针传递等方式传递参数。需要根据具体情况选择合适的传递方式,以避免不必要的拷贝和内存分配。
  • 线程函数的返回值:如果线程函数有返回值,需要确保返回值的正确性和线程安全性。可以使用std::future或std::async等机制来获取线程函数的返回值。

总之,向线程函数传递参数时,需要注意参数的类型、拷贝语义、线程安全、异常处理、传递方式和返回值等问题,以确保程序的正确性和性能。

三、转移线程归属权

线程运行的过程中会有出现线程归属权需要进行转移的情况:
(1)编写函数,创建线程,线程置于后台运行;但函数本身不等待线程完成,将这个线程的归属权向上移交给函数的调用者;
(2)想创建一个线程,将其归属权传入某个函数,由他负责等待该线程结束;
要想实现线程归属权的转移,可以使用std::move函数来实现;
举个简单的栗子:

#include <iostream>
#include <thread>void threadFunction(){//线程中执行的函数std::cout << "Thread is runnning!" <<std::endl;
}
int main(){//创建一个线程std::thread myThread(threadFunction);//转移线程所有权std::thread anotherThread = std::move(myThread);//等待另一个线程执行完毕anotherThread.join();return 0;//此时,myThread不再拥有线程的所有权,不能再join,//myThread.join();这一行会导致编译错误;
}

四、运行时选择线程数量

通过hardware_concurrency() 来确定系统上可以并发执行的线程数。
std::thread::hardware_concurrency() 是 C++11 标准库中的一个函数,它返回一个无符号整数,表示在给定的系统上可以并发执行的线程数。这个数字通常表示系统的核心数或逻辑处理器数。
这个函数在编程中有多种用途:
(1)线程数量选择:当你需要并行执行多个任务时,可以使用 std::thread::hardware_concurrency() 来确定最适合的线程数量。这通常是一个合理的起点,但你仍然需要考虑其他因素,如任务的工作负载、内存使用情况等。
(2)资源利用:通过使用与系统核心数相匹配的线程数,可以更有效地利用系统资源。过多的线程可能会导致上下文切换的开销增加,而太少的线程则可能无法充分利用系统资源。
(3)性能测试:在测试多线程应用程序的性能时,可以使用 std::thread::hardware_concurrency() 来确定基准线程数。然后,可以逐渐增加或减少线程数量,以观察性能的变化。
举个简单的栗子:

#include <iostream>  
#include <thread>  
#include <vector>  // 简单的任务函数  
void simpleTask(int threadId) {  std::cout << "Thread " << threadId << " is running." << std::endl;  
}  int main() {  // 获取系统的最大并发线程数  unsigned int maxConcurrentThreads = std::thread::hardware_concurrency();  std::cout << "Maximum concurrent threads: " << maxConcurrentThreads << std::endl;  // 创建线程的向量  std::vector<std::thread> threads(maxConcurrentThreads);  // 启动线程并为其分配简单的任务  for (int i = 0; i < maxConcurrentThreads; ++i) {  threads[i] = std::thread(simpleTask, i);  }  // 等待所有线程完成  for (auto& thread : threads) {  thread.join();  }  std::cout << "All threads completed." << std::endl;  return 0;  
}

五、识别线程

在C++多线程编程中,有2种方式能获取线程。
(1)在与线程关联的std::thread对象上调用成员函数get_id(),就可以获得该线程ID;
(2)当前线程的ID可以通过调用std::this_thread::get_id()方法便可以获得。
举个简单的栗子:

#include <iostream>
#include <thread>
//延时函数
void delay(int milliseconds) {std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
//方法一:调用成员函数std::this_thread::get_id()返回当前线程的ID
void print_id(){std::cout << std::this_thread::get_id() << std::endl;delay(100);
}
void hello_world(){std::cout << "Hello world!" << std::endl;
}
int main(){std::thread t1(print_id);std::thread t2(hello_world);//方法二:在与线程关联的std::thread对象上调用成员函数get_id()std::cout << "t2_id:" << t2.get_id() << std::endl;t1.join();t2.join();
}

六、总结

书山有路勤为径,学海无涯苦作舟。

七、参考书籍

《C++并发编程实战(第二版)》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/231855.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

英码科技受邀参加2023计算产业生态大会,分享智慧轨道交通创新解决方案

12月13-14日&#xff0c;“凝心聚力&#xff0c;共赢计算新时代”——2023计算产业生态大会在北京香格里拉饭店成功举办。英码科技受邀参加行业数字化分论坛活动&#xff0c;市场总监李甘来先生现场发表了题为《AI哨兵&#xff0c;为铁路安全运营站好第一道岗》的精彩主题演讲&…

系统学习Python——装饰器:使用和定义装饰器

分类目录&#xff1a;《系统学习Python》总目录 正如我们所看到的&#xff0c;Python本身带有扮演特殊角色的内置装饰器一一静态方法和类方法声明、property创建等。此外&#xff0c;很多流行的Python工具包括装饰器&#xff0c;可执行管理数据库或用户接口逻辑等任务。在这样的…

1951 年以来的美国ACIS 气候地图数据集(5 公里空间分辨率)

应用气候信息系统 (ACIS) NRCC NN ACIS是Applied Climate Information System的缩写&#xff0c;是由美国国家气象局&#xff08;NOAA&#xff09;开发的一种气候信息系统。ACIS气候地图是通过收集和整理全球的气象数据&#xff0c;利用计算机技术和数据分析方法生成的气候图表…

计算机组成原理-选择语句和循环语句的汇编表示

文章目录 选择语句jmpjxx示例&#xff1a;选择语句的机器级表示扩展&#xff1a;cmp指令的底层原理 循环语句使用条件转移指令实现循环用loop指令实现循环 选择语句 不一定知道指令的位置&#xff0c;所以jmp直接跳转到指令的位置很难办 jmp 标号相当于位置&#xff0c;名字…

解决win11杀毒(不能安装破解软件的问题)

1、下载火绒APP&#xff0c;打开火绒APP软件 2、点击菜单&#xff0c;选择安全设置 3、选择病毒防护&#xff0c;修改病毒处理方式为询问我 4、这样在解压激活的软件就不会被windows的杀毒软件自动删除了 5、问题解决了就点击三连吧

Mysql基础知识

第一章&#xff1a;数据库概述 前置:LAMP LNMP 企业架构&#xff0c;服务定位示例 车库 菜场篮子 生活中的数据微信&#xff08;视频、音频、图片文字&#xff09;、饿了吗、美团、qq、绝地求生、王者荣耀 一、数据库基本概念 1.1 数据 1&#xff09; 描述事物的符号记录称…

永久删除的文件如何恢复?这3个简单方法可以帮到你!

“我在清理电脑的过程中&#xff0c;一不小心就删除了一些很重要的文件和数据&#xff0c;很想通过某种方法将这些数据找回来&#xff0c;但是不知道应该如何操作&#xff0c;有朋友可以分享一下文件找回的简单方法吗&#xff1f;” 在日常生活和工作中&#xff0c;我们时常会遇…

正则表达式零宽断言

正则表达式零宽断言 工具类&#xff0c;正则表达式匹配文本内容正则表达式语法例子例子01零宽断言?< 不包含左边值? 不包含右边值例子 常用正则表达式校验数字的表达式校验字符的表达式 工具类&#xff0c;正则表达式匹配文本内容 /*** 正则表达式工具类*/ public class…

【JAVA基础(对象和封装以及构造方法)】----第四天

对象和封装以及构造方法 面向对象和面向过程面向过程面向对象 类与对象及其使用定义类创建一个对象&#xff0c;操作类补充&#xff08;成员变量和局部变量&#xff09; private 修饰类 封装练习编写类编写测试输出结果 面向对象和面向过程 面向过程 在了解面向对象之前先来了…

用户行为分析遇到的问题-ubantu16,hadoop3.1.3【更新中】

用户行为分析传送门 我的版本 ubantu16 hadoop 3.1.3 habse 2.2.2 hive3.1.3 zookeeper3.8.3 sqoop 1.46/1.47 我sqoop把MySQL数据往hbase导数据时候有问题 重磅&#xff1a;大数据课程实验案例&#xff1a;网站用户行为分析&#xff08;免费共享&#xff09; 用户行为分析-小…

VR党建:VR全景技术如何助力党建知识传播

导语&#xff1a; 随着科技的不断发展&#xff0c;虚拟现实技术逐渐深入人们生活的方方面面。VR全景技术作为一种全新的沉浸式体验方式&#xff0c;被广泛应用于娱乐、教育、医疗等领域。而在党建学习中&#xff0c;VR全景技术也展现出了巨大的潜力&#xff0c;成为了一种创新…

「数据结构」二叉树1

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;C启航 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 文章目录 &#x1f349;树&#x1f349;二叉树&#x1f34c;特殊二叉树&#x1f34c;二叉树的性质&#x1f34c;存储结构 &#x1f349;…

【PHP】身份证正则验证、校验位验证

目录 1.正则 简单正则 详细正则 2.校验位验证 1.正则 简单正则 function isValidIdCardNumber($idCardNumber) {// 身份证号长度为 15 位或 18 位$pattern /^(?:\d{15}|\d{17}[\dxX])$/;return preg_match($pattern, $idCardNumber); }$idCardNumber 12345678901234567…

Linux中用户名与UID、用户组名与GID的关系(为什么有用户名、用户组了,还要搞个UID、GID?)

文章目录 Linux中用户名与UID、用户组名与GID的关系1. 用户名和UID的区别1.1 用户名1.2 用户ID (UID) 2. 用户组名和GID的区别2.1 用户组名2.2 组ID (GID) 3. 为什么需要UID和GID Linux中用户名与UID、用户组名与GID的关系 在Linux系统中&#xff0c;用户名和用户组名主要用于…

ffmpeg入门之Windows开发之二(视频转码)

添加ffmpeg windows编译安装及入门指南-CSDN博客 的头文件和依赖库如下&#xff1a; main 函数如下&#xff1a; extern "C" { #ifdef __cplusplus #define __STDC_CONSTANT_MACROS #endif } extern "C" { #include <libavutil/timestamp.h> #in…

OpenCV-8RGB和BGR颜色空间

一. RGB和BGR 最常见的色彩空间就是RGB&#xff0c;人眼也是基于RGB的色彩空间去分辨颜色。 OpenCV默认使用的是BGR. BGR和RGB色彩空间的区别在于图片在色彩通道上的排列顺序不同。 二.HSV, HSL和YUV 1.HSV(HSB) OpenCV用的最多的色彩空间是HSV. Hue&#xff1a;色相&…

LeetCode(Hot100)——7:数字反转

复习 &#xff08;1&#xff09;char charAt(int index) 返回指定索引处的 char 值。 &#xff08;2&#xff09;String substring(int beginIndex, int endIndex) 返回一个新字符串&#xff0c;它是此字符串的一个子字符串。 &#xff08;3&#xff09;String substring(int …

前端项目配置下载源npm, yarn,pnpm

前端项目配置下载源 npm: npm config set registry registryhttps://registry.npmmirror.com -g验证: npm config get registry yarn: yarn config set registry registryhttps://registry.npmmirror.com -gyarn config get registryyarn找不到, 需要管理员在命令行: set-exec…

【分享】5种方法将Excel设置为“只读”

将Excel表格设置以“只读方式”打开&#xff0c;可以提醒或者防止表格被随意改动&#xff0c;今天小编来分享一下将Excel设置为“只读”的5种方法。 方法一&#xff1a;通过“保护工作簿”设置 首先&#xff0c;打开Excel表格依次点击菜单选项卡【文件】→【信息】→【保护工作…

【无标题】CTF之SQLMAP

拿这一题来说 抓个包 复制报文 启动我们的sqlmap kali里边 sqlmap -r 文件路径 --dump --dbs 数据库 --tables 表