当前位置: 首页 > news >正文

C++11 多线程编程-小白零基础到手撕线程池

提示:文章

文章目录

  • 前言
  • 一、背景
  • 二、
    • 2.1
    • 2.2
  • 总结

前言

前期疑问:
本文目标:


一、背景

来源于b站视频

C++11 多线程编程-小白零基础到手撕线程池

学习来源:https://www.bilibili.com/video/BV1d841117SH/?p=2&spm_id_from=pageDriver&vd_source=1a54eaaaa0e36b9ef70e2dbe59d5b137

http://www.seestudy.cn/?list_9/35.html

相关知识点

thread创建线程
join
detach
joinable
std::ref
智能指针在头文件中

二 、互斥量

自己写的代码

2.1 互斥量

#include <iostream>
#include <thread>int counter = 0;void fun()
{for (int i = 0; i < 10000; i++){counter++;}
}int main() {std::thread t1(fun);std::thread t2(fun);t1.join();t2.join();std::cout << "a:" << counter << std::endl;
}

没有实现竞争访问a导致结果不是2000的情况,我这边的打印结果是20000。

看有的评论说加一个0实现了,我加了好几个0还是没有实现。

有的评论说现在编译器都是2000了,不明所以,继续向下看吧。


第二天来用了课程对应的代码重新跑了一下,同时循环次数尝试加了个0。又出现了变量打印的值不是20000的情况。而我的myFirstTest工程依然出现不了预期的情况,先不管了。

2.2 互斥量锁死

#include <iostream>
#include <thread>
#include <mutex>std::mutex m1, m2;int counter = 0;//死锁
void fun1()
{for (int i = 0; i < 5000; i++){m1.lock();m2.lock();m1.unlock();m2.unlock();}
}void fun2()
{for (int i = 0; i < 5000; i++){m2.lock();m1.lock();m1.unlock();m2.unlock();}
}void fun()
{for (int i = 0; i < 1000000; i++){counter++;}
}int main() {std::thread s1(fun1);std::thread s2(fun2);s1.join();s2.join();std::thread t1(fun);std::thread t2(fun);t1.join();t2.join();std::cout << "a:" << counter << std::endl;
}

循环次数50、500都不会锁死,5000会锁死。

视频中给出的防止死锁的解决办法是,每个线程都先获取m1,继续获取m2。顺序获取。可以防止死锁。

(2024年9月29日17:11:45 今天再看代码,理解之前写的【顺序获取。可以防止死锁。】,应该是修改上述代码为都先获取m1,再获取m2)

5、std::lock_guard

#include <iostream>
#include <thread>
#include <mutex>std::mutex tex;
std::mutex m1, m2;int counter = 0;void fun()
{for (int i = 0; i < 1000000; i++){std::lock_guard<std::mutex> lg(tex);counter++;}
}int main() {std::thread t1(fun);std::thread t2(fun);t1.join();t2.join();std::cout << "a:" << counter << std::endl;
}

lock_guard源码

template<class _Mutex>class lock_guard{	// class with destructor that unlocks a mutex
public:using mutex_type = _Mutex;explicit lock_guard(_Mutex& _Mtx): _MyMutex(_Mtx){	// construct and lock_MyMutex.lock();}lock_guard(_Mutex& _Mtx, adopt_lock_t): _MyMutex(_Mtx){	// construct but don't lock}~lock_guard() noexcept{	// unlock_MyMutex.unlock();}lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete;
private:_Mutex& _MyMutex;};

针对这个源码,其中_MyMutex是私有成员变量。explicit表示禁止隐式转换。还涉及到构造函数重载、禁用构造、禁用拷贝函数。

2024年9月29日17:21:30

这篇文章是在之前写的,后面我又因为看代码疑惑信号量的使用,又写了下面的一篇文章:关于多线程unique_lock和guard_lock,而实际我在写关于多线程unique_lock和guard_lock这篇文章的时候也没有想起这边写的关于信号量的知识点。然后这次在看到这篇文章就想到了后面写的文章关于多线程unique_lock和guard_lock。但是我也忘了关于多线程unique_lock和guard_lock这篇文章的内容了。所以两篇文章结合看了下,加深了理解。

… …

七、 std::call_once与其使用场景

涉及到单例类。两种模式

饿汉模式和懒汉模式。

教程中以log类举例子,

static Log& GetInstance()
{static Log log;return log;
}
//这种是饿汉模式,构建类的时候就创建Log静态类对象。
//然后我的疑问是每次GetInstance的时候,不会多次创建log对象吗?实际是log是静态成员,只有一个

下面的是懒汉对象

static Log& GetInstance()
{static Log *log = nullptr;if(!log) {log = new Log();}return log;
}

上面代码我的疑问是,每次GetInstance的时候不会多次new对象吗?实际是不会,因为if(!log)做了判断,log不为空就不会再new对象。

针对饿汉模式在构造函数中创建静态变量,这边为什么不会继续创建对象,我查了资料,没看到啥解释。

但是看到另外两个点。

第一个就是懒汉模式申请的堆内存如果释放会内存泄漏。我觉得可以在析构函数释放就可以。

第二个就是帖子提到单例类模式线程不安全,主要是懒汉模式,多个线程读取if(!log)中log变量的时候,可能会多次申请堆内存。可以在if(!log)增加互斥锁,但是会影响效率。

参考文档:https://blog.csdn.net/code_feien/article/details/110423021

针对上述静态变量的问题,我还写了下面的测试代码

#include <iostream>using namespace std;int getData()
{static int test = 0;test++;return test;
}int main() {std::cout << "Hello, World!" << std::endl;for(int i = 0; i < 10; i++){int num = getData();cout << num << endl;}return 0;
}//预测结果是10
//实际打印结果1——10,确实和预期一样
//码可以理解为静态变量已经创建生命周期一直存在直到程序结束

九、 线程池

threadPool.h文件

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <queue>class ThreadPool {
public:ThreadPool(int numThreads) : stop(false) {for (int i = 0; i < numThreads; ++i) {threads.emplace_back([this] {while (true) {std::unique_lock<std::mutex> lock(mutex);condition.wait(lock, [this] { return stop || !tasks.empty(); });if (stop && tasks.empty()) {return;}std::function<void()> task(std::move(tasks.front()));tasks.pop();lock.unlock();task();}});}}~ThreadPool() {{std::unique_lock<std::mutex> lock(mutex);stop = true;}condition.notify_all();for (std::thread& thread : threads) {thread.join();}}template<typename F, typename... Args>void enqueue(F&& f, Args&&... args) {std::function<void()> task(std::bind(std::forward<F>(f), std::forward<Args>(args)...));{std::unique_lock<std::mutex> lock(mutex);tasks.emplace(std::move(task));}condition.notify_one();}private:std::vector<std::thread> threads;std::queue<std::function<void()>> tasks;std::mutex mutex;std::condition_variable condition;bool stop;
};

main.cpp

#include "threadPool.h"int main(void)
{ThreadPool pool(4);for (int i = 0; i < 8; ++i) {pool.enqueue([i] {std::cout << "Task " << i << " is running in thread " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Task " << i << " is done" << std::endl;});}return 0;
}

关于上述代码设计到lamda等知识点

9.1 std:bind

其中关于std::bind,见这篇文章: std::bind的讲解

9.2 std::forward

然后还有一个知识点就是std::forward,

参考这篇文章:std::forward与完美转发详解

然后我继续查找关于std::forward知识点,看到这个例子

std::forward入门

std::forward 是 C++11 引入的标准库函数,用于实现完美转发。完美转发意味着在函数模板内,保持传递给函数的参数的左右值属性和常量属性。

下面是一个简单的 std::forward 使用示例:

#include <iostream>
#include <utility>// 这是一个函数模板,用来展示完美转发的效果
template<typename T>
void printValue(T&& val) {// 使用std::forward保留val的左右值属性和常量属性std::cout << (std::is_same<T, int&>::value ? "LValue: " : "RValue: ")<< std::forward<T>(val) << std::endl;
}int main() {int a = 5;printValue(a); // 将a作为左值传递printValue(std::move(a)); // 将a转换为右值,并传递return 0;
}

在这个例子中,printValue 是一个函数模板,它接受一个模板参数可以是任何类型的右值引用。当我们调用 printValue 时,我们可以传递一个左值或者右值。std::forward 保证在函数内部,我们传入的参数在模板实例化时保持其原有的左右值属性和常量属性。

输出结果将会是:

LValue: 5
RValue: 5

上述我不知道为什么上面的例子就能表现出std::forward的完美转发,我目前理解为std::forward可以实现左值和右值的转发。

然后上面有涉及到一个点就是std::is_same,关于std::is_same参考这篇文章:C++ 语言 std::is_same

9.3 std::function

std::function详解

我在纠结**std::function<void()> task(std::move(tasks.front()));**这个std::function<void()>是什么意思。查到下面的一个文章

c++ std::function的使用

其中一个示例

#include <functional>void function1()
{std::cout << "This is function1." << std::endl;
}int main()
{// 使用函数指针初始化 std::function 对象std::function<void()> f1 = function1;f1();
}//打印结果
//This is function1.

我理解大概意思就是std::function<void()>就是可以接收一个void f()函数。

9.4 对这个线程池的理解

lamda写法不是很理解,一直想找一个c++写的线程池代码没找到。

看了b站陈子青的视频,使用了他写的线程池,即有很多lamda表达式的线程池代码。可以实现900多个文件的正常读写。


总结

未完待续

相关文章:

  • 秋招内推--招联金融2025
  • 论文阅读:多模态医学图像融合方法的研究进展
  • 负载均衡架构解说
  • C++ Linux多进程同步-命名信号量
  • HarmonyOS NEXT:实现电影列表功能展示界面
  • IDEA相关设置总结
  • 03Frenet与Cardesian坐标系(Frenet转Cardesian公式推导)
  • Win10 QT 配置Android开发环境-jdk/sdk/gradle
  • 探究Spring的单例设计模式--单例Bean
  • 25中国烟草校园招聘面试问题总结 烟草面试全流程及面试攻略
  • 国外电商系统开发-需求记录
  • 【C++】异常处理
  • Android Stuido中编译信息出现乱码的解决方式
  • ClickHouse | 查询
  • C++ | Leetcode C++题解之第446题等差数列划分II-子序列
  • Cumulo 的 ClojureScript 模块已经成型
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • java小心机(3)| 浅析finalize()
  • node-glob通配符
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Python_网络编程
  • ReactNative开发常用的三方模块
  • Redis的resp协议
  • SpingCloudBus整合RabbitMQ
  • SpringBoot 实战 (三) | 配置文件详解
  • 利用DataURL技术在网页上显示图片
  • 全栈开发——Linux
  • 如何进阶一名有竞争力的程序员?
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 入门级的git使用指北
  • 我的zsh配置, 2019最新方案
  • 无服务器化是企业 IT 架构的未来吗?
  • 优秀架构师必须掌握的架构思维
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • 白色的风信子
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • 阿里云服务器如何修改远程端口?
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (done) 声音信号处理基础知识(11) (Complex Numbers for Audio Signal Processing)
  • (MTK)java文件添加简单接口并配置相应的SELinux avc 权限笔记2
  • (TOJ2804)Even? Odd?
  • (Windows环境)FFMPEG编译,包含编译x264以及x265
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (函数)颠倒字符串顺序(C语言)
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (转)Unity3DUnity3D在android下调试
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET MVC第三章、三种传值方式
  • .net企业级架构实战之7——Spring.net整合Asp.net mvc
  • :class的用法及应用
  • @EnableWebSecurity 注解的用途及适用场景
  • @transactional 方法执行完再commit_当@Transactional遇到@CacheEvict,你的代码是不是有bug!...