C++系列十:日常学习-多线程

这篇具有很好参考价值的文章主要介绍了C++系列十:日常学习-多线程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录
  • 介绍:
  • 理论:
  • 比喻理解
  • 案例:
  • 生产者-消费者问题:

介绍:

C++ 是一种支持多线程编程的编程语言,它提供了丰富的多线程支持来充分利用现代多核处理器的性能。
C++ 多线程编程通常使用标准库中的 头文件以及其他相关的标准库组件来实现。

理论:

  1. 常用的类:
    std::thread:用于创建和管理线程等等
    std::this_thread: 命名空间中的函数来处理线程的等待和分离等等
    互斥锁(std::mutex)、条件变量(std::condition_variable)和原子操作(std::atomic)
    mutex:是能用于保护共享数据免受从多个线程同时访问的同步原语。
    lock_guard:创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。
    timed_mutex: 具有超时功能,它允许线程等待一段时间,如果在指定时间内没有获得锁,它可以决定是否继续等待或者放弃。这有助于避免无限期的等待和资源泄漏。
    recursive_mutex:递归锁,即同一个线程可以多次获取锁,而不会因为递归调用而陷入情感僵局。

  2. 线程池:
    线程池是一种管理和复用线程的技术,以避免频繁创建和销毁线程。C++ 标准库中没有直接提供线程池的实现,但你可以使用第三方库或自己编写一个简单的线程池。

  3. 请注意,多线程编程可能会引入一些复杂性和潜在的问题,如竞态条件和死锁。因此,确保充分理解多线程编程的概念和最佳实践,并使用适当的同步机制来确保线程安全是非常重要的。

比喻理解

//std::mutex(互斥锁)
想象有两个人(线程)正在尝试访问一个房间(共享资源)。这个房间只能容纳一个人,因此在进入房间之前,每个人必须先拿到一把特殊的钥匙,这把钥匙代表了 std::mutex。

//lock_guard
lock_guard 就像一个线程试图表白,即尝试获取锁。只有成功获取锁(成功表白)后,线程才能进一步执行。
如果一个线程已经拥有了锁(已经表白了),其他线程(其他人)试图再次获取相同的锁,但它们将等待,就好像在等待对方的表白。
如果没有合适的时机释放锁(表白的机会),那么所有线程都会陷入等待,就像互相等待对方表白,最终导致进展受阻,就像爱情无法进展一样

//timed_mutex 
timed_mutex 就像是两个人之间的约会,但是双方都知道约会有一个截止时间。如果一方在截止时间前没有出现,另一方可以不再等待,而是继续自己的事情。
想象两个人计划在咖啡厅见面,但是他们都有其他重要的事情要做。他们各自持有一把锁,代表他们对约会的需求。他们可以尝试获取对方的锁,表示他们准备好见面。
如果一个人等待太长时间,另一个人没有出现,那么等待的人可以放弃,释放锁并继续他们的日常生活。
这种方式确保了两个人都不会无限期地等待对方,因为他们知道有一个截止时间。

//recursive_mutex
设有两个人,分别是 Alice 和 Bob,他们正在探讨一个复杂的情感问题。他们希望能够坦诚地分享自己的情感,但有时情感问题会牵涉到其他情感问题。他们决定使用递归的方式来探讨这些问题,允许在一个情感问题中引用另一个情感问题。
Alice 代表 std::recursive_mutex 的锁定者,她会开始探讨一个情感问题,然后发现这个问题与其他情感问题相关。于是,她会调用 Bob 来帮助她解决与该问题相关的情感问题。
Bob 代表递归调用的情感问题处理者。当 Alice 请求帮助时,他愿意深入探讨并尝试解决与该问题相关的情感问题。如果在解决问题时涉及到其他情感问题,他会再次调用自己来处理这些问题。
每个情感问题就像是一把锁,可以锁住该问题,确保只有一个人(线程)能够深入探讨并解决问题,而其他问题仍然可以并行探讨。

//死锁
**死锁情况:**
- 有两个线程 A 和 B。
- 它们都需要某些资源或锁来执行操作。
- 线程 A 获取了资源1,但需要资源2才能继续执行。
- 同时,线程 B 获取了资源2,但需要资源1才能继续执行。
- 由于线程 A 拥有资源1并且不释放,而线程 B 拥有资源2并且不释放,它们互相等待对方释放所需的资源,导致死锁。

**比喻:**
- 我和你都需要对方的表白,才能进行下一步。
- 我需要你的表白才能进行下一步。
- 你需要我的表白才能进行下一步。
- 我的表白没有向你表达,而且你的表白没有向我表达(你等我开口,我等你开口),所以互相等着对方的表白,最终导致他们之间的爱情无法进展。

在两种情况下,核心问题都是互相依赖,互相等待对方才能继续,但由于没有解决依赖关系或释放资源,进度被阻塞,导致无法继续前进。这种相互等待和僵局是死锁的经典特征。

案例:

std::thread:
join() //阻塞当前线程,直到目标线程执行完毕。
detach() //将线程分离,使其成为后台线程,不再受到 join() 的控制。


//创建互斥锁,并在访问共享资源之前进行锁定。
std::mutex mtx;
mtx.lock();   // 锁定互斥锁
// 访问共享资源
mtx.unlock(); // 解锁互斥锁


//自动管理锁的生命周期,以确保在离开作用域时自动释放锁。
std::mutex mtx;
{
    std::lock_guard<std::mutex> lock(mtx); // 自动锁定和解锁
    // 访问共享资源
} // 离开作用域时自动解锁


//条件变量用于在线程之间进行通信和同步。它们允许一个线程等待另一个线程发出的通知,以执行某些操作。
std::condition_variable cv;
// 线程1等待通知
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock); // 阻塞线程1,直到收到通知
// 线程2发送通知
cv.notify_one(); // 通知线程1


//原子操作:
std::atomic<int> counter(0);
counter.fetch_add(1, std::memory_order_relaxed); // 原子递增操作


//案例1:
#include <iostream>
#include <thread>
void myFunction() {
    // 线程1执行的代码
}
void myFunction1(int value) {
    // 线程2执行的代码
}
int main() {
    std::thread t1(myFunction); // 创建新线程并启动
    std::thread t2(myFunction1,100); // 创建新线程并启动
    t1.join(); // 等待线程完成
    t2.join(); // 等待线程完成
    return 0;
}

//案例2:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void myFunction(int& counter) {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> lock(mtx); // 使用互斥锁保护共享资源
        counter++;
    }

int main() {
    int counter = 0;
    std::thread t1(myFunction, std::ref(counter));
    std::thread t2(myFunction, std::ref(counter));
    t1.join();
    t2.join();
    std::cout << "Counter: " << counter << std::endl;//Counter: 2000
    return 0;
}

//例子3
#include <thread>
#include <mutex>
#include <iostream>
int g_i = 0;//int g_i;不赋值也可以,全局变量和静态变量有默认值
std::mutex g_i_mutex;  // 保护 g_i
void safe_increment()
{
    std::lock_guard<std::mutex> lock(g_i_mutex);
    ++g_i;
    std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
    // g_i_mutex 在锁离开作用域时自动释放
}
int main()
{
    std::cout << "main: " << g_i << '\n';
    std::thread t1(safe_increment);
    std::thread t2(safe_increment);
    t1.join();//阻塞线程
    t2.join();//阻塞线程
    std::cout << "main: " << g_i << '\n';
}


//例子4:
#include <iostream>
#include <map>
#include <string>
#include <chrono>
#include <thread>
#include <mutex>
std::map<std::string, std::string> g_pages;
std::mutex g_pages_mutex;
void save_page(const std::string& url)
{
    // 模拟长页面读取
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::string result = "fake content";

    std::lock_guard<std::mutex> guard(g_pages_mutex);
    g_pages[url] = result;
}
int main()
{
    std::thread t1(save_page, "http://foo");
    std::thread t2(save_page, "http://bar");
    t1.join();
    t2.join();

    // 现在访问g_pages是安全的,因为线程t1/t2生命周期已结束
    for (const auto& pair : g_pages) {
        std::cout << pair.first << " => " << pair.second << '\n';
    }
}


//例子5:
#include <iostream>
#include <thread>
#include <mutex>
class X {
    std::recursive_mutex m;
    std::string shared;
public:
    void fun1() {
        std::lock_guard<std::recursive_mutex> lk(m);
        shared = "fun1";
        std::cout << "in fun1, shared variable is now " << shared << '\n';
    }
    void fun2() {
        std::lock_guard<std::recursive_mutex> lk(m);
        shared = "fun2";
        std::cout << "in fun2, shared variable is now " << shared << '\n';
        fun1(); // 递归锁在此处变得有用
        std::cout << "back in fun2, shared variable is " << shared << '\n';
    };
};
int main()
{
    X x;
    std::thread t1(&X::fun1, &x);
    std::thread t2(&X::fun2, &x);
    t1.join();
    t2.join();
}


//死锁:
std::mutex mutex1;
std::mutex mutex2;
void thread1() {
   std::lock_guard<std::mutex> lock1(mutex1);
   std::this_thread::sleep_for(std::chrono::milliseconds(1));
   std::lock_guard<std::mutex> lock2(mutex2); // 死锁
}
void thread2() {
   std::lock_guard<std::mutex> lock2(mutex2);
   std::this_thread::sleep_for(std::chrono::milliseconds(1));
   std::lock_guard<std::mutex> lock1(mutex1); // 死锁
}
这里有两个线程,`thread1` 和 `thread2`,它们尝试获取两个不同的互斥锁,但获取的顺序不同。如果 `thread1` 先获取 `mutex1`,而 `thread2` 先获取 `mutex2`,那么它们会互相等待对方释放锁,导致死锁。

生产者-消费者问题:

这就有意思多了,多看多吸收,多悟
不理解得就多问问Chatjpt。文章来源地址https://www.toymoban.com/news/detail-695155.html

//一对一:
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

std::queue<int> buffer;
std::mutex mtx;
std::condition_variable cv;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        // 锁定互斥锁以保护共享资源
        {
            std::lock_guard<std::mutex> lock(mtx);
            buffer.push(i);
            std::cout << "Produced: " << i << std::endl;
        }
        // 通知消费者线程有新数据可用
        cv.notify_one();
    }
}

void consumer() {
    for (int i = 0; i < 10; ++i) {
        // 锁定互斥锁,等待条件变量通知
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !buffer.empty(); });
        // 消费数据
        int data = buffer.front();
        buffer.pop();
        // 解锁互斥锁
        lock.unlock();
        std::cout << "Consumed: " << data << std::endl;
    }
}

int main() {
    std::thread producerThread(producer);
    std::thread consumerThread(consumer);
    producerThread.join();
    consumerThread.join();
    return 0;
}

//一对多:
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <vector>
std::queue<int> buffer;
std::mutex mtx;
std::condition_variable cv;
const int numProducers = 3; // 多个生产者线程

void producer(int id) {
    for (int i = 0; i < 5; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        {
            std::lock_guard<std::mutex> lock(mtx);
            int data = id * 100 + i;
            buffer.push(data);
            std::cout << "Producer " << id << " produced: " << data << std::endl;
        }
        cv.notify_one();
    }
}

void consumer() {
    for (int i = 0; i < 15; ++i) { // 总共消费 15 个数据项
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !buffer.empty(); }); // 等待缓冲区非空
        int data = buffer.front();
        buffer.pop();
        lock.unlock();
        std::cout << "Consumer consumed: " << data << std::endl;
    }
}

int main() {
    std::vector<std::thread> producerThreads;
    for (int i = 0; i < numProducers; ++i) {
        producerThreads.push_back(std::thread(producer, i));
    }
    std::thread consumerThread(consumer);
    for (std::thread& thread : producerThreads) {
        thread.join();
    }
    consumerThread.join();
    return 0;
}


//多对多
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <vector>
std::queue<int> buffer; // 缓冲区队列
std::mutex mtx; // 互斥锁,用于保护共享资源
std::condition_variable cv; // 条件变量,用于线程间通信

const int numProducers = 2; // 多个生产者线程
const int numConsumers = 2; // 多个消费者线程
const int bufferSize = 10; // 缓冲区大小
void producer(int id) {
    for (int i = 0; i < 5; ++i) { // 生产者每个生成 5 个数据项
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        {
            std::unique_lock<std::mutex> lock(mtx);
            // 等待缓冲区有空间可用
            cv.wait(lock, []{ return buffer.size() < bufferSize; });
            int data = id * 100 + i;
            buffer.push(data);
            std::cout << "Producer " << id << " produced: " << data << std::endl;
        }
        cv.notify_all(); // 通知所有等待的线程
    }
}

void consumer(int id) {
    for (int i = 0; i < 5; ++i) { // 消费者每个消费 5 个数据项
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        {
            std::unique_lock<std::mutex> lock(mtx);
            // 等待缓冲区非空
            cv.wait(lock, []{ return !buffer.empty(); });
            int data = buffer.front();
            buffer.pop();
            lock.unlock(); // 解锁互斥锁,允许其他线程进入临界区
            std::cout << "Consumer " << id << " consumed: " << data << std::endl;
        }
        cv.notify_all(); // 通知所有等待的线程
    }
}

int main() {
    std::vector<std::thread> producerThreads;
    std::vector<std::thread> consumerThreads;
    for (int i = 0; i < numProducers; ++i) {
        // 创建生产者线程,并传递唯一的标识符 i
        producerThreads.push_back(std::thread(producer, i));
    }
    for (int i = 0; i < numConsumers; ++i) {
        // 创建消费者线程,并传递唯一的标识符 i
        consumerThreads.push_back(std::thread(consumer, i));
    }
    for (std::thread& thread : producerThreads) {
        thread.join(); // 等待生产者线程结束
    }
    for (std::thread& thread : consumerThreads) {
        thread.join(); // 等待消费者线程结束
    }
    return 0;
}

到了这里,关于C++系列十:日常学习-多线程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【STM32&RT-Thread零基础入门】 4. 线程介绍(理论)

    前文中的最后一个任务发现,一个main()函数很难同时实现按键功能和闪灯功能,就好像人很难同时完成左手画圆右手画方一样,这种情况可以安排一人去画圆、一人去画方,并行进行就很容易了,两人各司其职,互不干扰。 操作系统中,一个线程就像做事的一个人。一个操作

    2024年02月12日
    浏览(37)
  • Jmeter系列-环境部署、详细介绍、安装目录介绍(1)

    http://jmeter.apache.org/下载最新版本的 JMeter,解压文件到任意目录 1、下载(注意选择操作系统对应的位数32/64) 官网 :http://www.oracle.com 2、安装(一键式) ,所有步骤选择项默认选择项。 3、配置环境变量 JAVA_HOME=JDK完整安装路径 环境变量Path添加:%JAVA_HOME%bin;%JAVA_HOME%jrebin;

    2024年02月09日
    浏览(42)
  • 多线程系列(十七) -线程组介绍

    在之前的多线程系列文章中,我们陆陆续续的介绍了 Thread 线程类相关的知识和用法,其实在 Thread 类上还有一层 ThreadGroup 类,也就是线程组。 今天我们就一起来简单的聊聊 线程组 相关的知识和用法。 线程组,简单来说就是多个线程的集合,它的出现主要是为了更方便的管

    2024年03月12日
    浏览(59)
  • RK3568平台开发系列讲解(Linux系统篇)Linux 目录结构介绍

    🚀返回专栏总目录 沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们从目录管理入手,会更直观的理解 linux 的目录结构。 Linux 整个文件系统是以“ / ”目录开始,根目录是最顶层,前面讲根目录和家目录概念的时候已经提到了。它下边包括众多的目录,这些

    2023年04月13日
    浏览(58)
  • 【强化学习理论】状态价值函数与动作价值函数系列公式推导

    由于时常对状态价值函数与动作价值函数之间的定义区别、公式关系迷惑不清,此次进行梳理并作记录。 理解公式推导需要先了解基础定义中几个概念。 奖励函数 奖励函数 有两种记法。 ①记作 r ( s ) r(s) r ( s ) ,表示某状态 s s s 的奖励,指:转移到该状态时能够获得的奖励

    2024年02月10日
    浏览(47)
  • C/C++系列系统学习目录

    友情链接:专栏地址 编程规范:C/C++语言编程规范 章节 内容 1.初识C语言 【C语言篇】初识C语言 2.C语言最基础入门 【C语言篇】C语言最基础入门 3.C语言的输入输出相关知识 【C语言篇】C语言的输入/输出相关知识 4.C语言的if选择结构和循环结构 【C语言篇】C语言的if选择结构

    2024年02月12日
    浏览(41)
  • Jmeter系列-并发线程Concurrency Thread Group的介绍(7)

    Concurrency Thread Group提供了用于配置多个线程计划的简化方法,该线程组目的是为了保持并发水平,意味着如果并发线程不够,则在运行线程中启动额外的线程 Concurrency Thread Group提供了更好的用户行为模拟,因为它使您可以更轻松地控制测试的时间,并创建替换线程以防线程在

    2024年02月07日
    浏览(37)
  • C++学习目录

    第一章 - C++介绍 第二章 - 数据类型与运算 第三章 - 选择与循环结构 第四章 - 函数 第五章 - 数组 第六章 - 指针与引用 第七章 - 数据结构 第八章 - 面向对象 第九章 - 类与对象的使用 第十章 - 面向对象三大特点 第十一章 - 输入输出流 第十二章 - 异常处理与命名空间 第十三章

    2024年02月07日
    浏览(27)
  • 【vim 学习系列文章 5 - cscope 过滤掉某些目录】

    上篇文章:【vim 学习系列文章 4 - vim与系统剪切板之间的交互】 下篇文章:【vim 学习系列文章 6 – vim 如何从上次退出的位置打开文件】 第一步 创建自己的 cscope 脚本 ~/.local/bin/cscope.sh ,如下: 我的这个脚本首先去区分当前执行 cscope 命令的目录是 rt-thread 目录还是 linux 目

    2024年02月12日
    浏览(82)
  • HarmonyOS鸿蒙学习基础篇 - 项目目录和文件介绍

    ├── hvigor //存储购置信息的文件,主要用于发布打包 ├── idea  //开发工具相关配置可忽略 ├── AppScope //工程目录 全局公共资源存放路径  │   └── resources   │   │   └── base │   │   │   └── element //常亮存放 │   │   │       └── string.json //保

    2024年01月21日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包