小白零基础--CPP多线程

news2025/2/3 7:54:20

进程

  • 进程就是运行中的程序
  • 线程=进程中的进程

1、C++11 Thread线程库基础

#include <iostream>
#include <thread>
#include<string>



void printthread(std::string msg){
  std::cout<<msg<<std::endl;
  for (int i = 0; i < 1000; i++)
  {
    std::cout<<"my  "<<i<<std::endl;
  }
  
  return;
}



int main(){
   //std::thread t(入口)
   //1、创建线程
    std::thread t(printthread,"hello thread");
   //2、保证等待线程结束,主线程在结束
   // t.join();
   //3、分离线程
   //t.detach();
   //4、joinable 判断是否可以调用join
   bool isjoin = t.joinable();
   if(isjoin){
      t.join();
   }
  std::cout<<"over"<<std::endl;
   system( "pause");
   
    return 0;
}

2、线程函数中的数据未定义错误

2.1 临时变量

错误例子

#include <iostream>
#include <thread>


void foo(int& x){
   x+=1;
}



int main(){
   //
   std::thread t(foo,1);
   t.join();
   system( "pause");
   
    return 0;
}

正确方案

#include <iostream>
#include <thread>


void foo(int& x){
   x+=1;
}



int main(){
   //
   int i=1;
   std::thread t(foo,std::ref(i));
   t.join();



  std::cout<<i<<std::endl;
   system( "pause");

    return 0;
}

2.2 传递指针/引用指向局部变量

2.1 的 i 在 main中,那要是不在呢?

#include <iostream>
#include <thread>

std::thread t;
void foo(int& x){
   x+=1;
}

void externi(){
  
    int i=1;
   t=std::thread (foo,std::ref(i));
}

int main(){
   //
   externi();
   t.join();



 
   system( "pause");

    return 0;
}

会报错
那怎么办呢?

#include <iostream>
#include <thread>

std::thread t;
 int i=1;
void foo(int& x){
   x+=1;
}

void externi(){
  
   
   t=std::thread (foo,std::ref(i));
}

int main(){
   //
   externi();
   t.join();



  std::cout<<i<<std::endl;
   system( "pause");

    return 0;
}

2.3 参数被提前手动释放

智能指针

#include <iostream>
#include <thread>
#include <memory>

class  myclass
{
private:
    /* data */
public:
    void foo(){
        std::cout<<"mememem"<<std::endl;
    }
};



int main(){
   std::shared_ptr<myclass> a=std::make_shared<myclass> ();
   std::thread t(&myclass::foo,a);

   system( "pause");
    return 0;
}

2.4 类的private函数

友元

#include <iostream>
#include <thread>
#include <memory>

class  myclass
{
private:
    friend void foo_thread();
     void foo(){
        std::cout<<"mememem"<<std::endl;
    }
public:
   
};
void foo_thread(){

    std::shared_ptr<myclass> a=std::make_shared<myclass> ();
    std::thread t(&myclass::foo,a);
    t.join();
}
int main(){
    foo_thread();
   system( "pause");
    return 0;
}

3互斥量

3.1数据共享–数据竞争问题

#include <iostream>
#include <thread>

int a = 0;
void func(){
    for (int i = 0; i < 1000000; i++)
    {
        a+=1;
    }
    
}
int main(){
   std::thread t1(func);
   std::thread t2(func);
   t1.join();
   t2.join();
  std::cout<<a<<std::endl;
   system( "pause");
    return 0;
}

3.2 互斥锁

#include <iostream>
#include <thread>
#include <mutex>
int a = 0;

std::mutex mt;
void func(){
    for (int i = 0; i < 1000000; i++)
    { 
        mt.lock();
        a+=1;
        mt.unlock();
    }
    
}
int main(){
   std::thread t1(func);
   std::thread t2(func);
   t1.join();
   t2.join();
  std::cout<<a<<std::endl;
   system( "pause");
    return 0;
}

3.3 理解线程安全

4互斥量死锁

4.1 死锁的概念

#include <iostream>
#include <thread>
#include <mutex>
using   namespace std;

mutex m1,m2;
void func1(){
    for (int i = 0; i < 100000; i++)
    { 
        m1.lock();
        m2.lock();
        m1.unlock();
        m2.unlock();
    }
    
    
}
void func2(){
       for (int i = 0; i < 100000; i++)
    { 
    m1.lock();
    m2.lock();
    m2.unlock();
    m1.unlock();
    }
}
int main(){
   thread t1(func1);
   thread t2(func2);
   t1.join();
   t2.join();
   
   cout<<"over<<"<<endl;
   system( "pause");
    return 0;
}

4.2 解决方案

5 lock_guard 和 std::unique_lock

5.1 lock_guard

#include <iostream>
#include <thread>
#include <mutex>
using   namespace std;
int a=0;
mutex m1;
void func1(){
    for(int i=0;i<10000;i++)
    {lock_guard<mutex> gm(m1);
      a++;
    
    }  
}

int main(){
   thread t1(func1);
   t1.join();
   cout<<a<<endl;
   system( "pause");
    return 0;
}

5.2 std::unique_lock

#include <iostream>
#include <thread>
#include <mutex>
using   namespace std;
int a=0;
mutex m1;
void func1(){
    for(int i=0;i<10000;i++)
    {
    unique_lock<mutex> gm(m1);
    a++;
    }  
}

int main(){
   thread t1(func1);
   t1.join();
   cout<<a<<endl;
   system( "pause");
    return 0;
}

6 call_once

6.1 单例模式

6.2 例子:日志类

#include <iostream>
#include <thread>
#include <mutex>
using   namespace std;
class Log{
public:
    Log(){};
    Log(const Log& log)=delete;
    Log &operator =(const Log& log)=delete;
    static Log& GetInstance(){
             static Log  log;//懒汉模式
             return log;
            //饿汉模式
            /**
            static Log   *log=nullptr;
            if(!log) log = new Log;
            return *log;
              */
    }
    void PrintLog(string msg){
            cout << __TIME__ <<" " <<msg<<endl;
    }
};

int main(){
   
   Log::GetInstance().PrintLog("error");

   system( "pause");
    return 0;
}

6.3 call_once

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

using namespace std;

// 需要一次性初始化的共享资源
class DatabaseConfig {
private:
    string serverAddress;
    int port;

    DatabaseConfig() : serverAddress("127.0.0.1"), port(3306) {
        cout << "数据库配置初始化完成!" << endl;
    }

public:
    static DatabaseConfig& getInstance() {
        static once_flag initFlag;
        static DatabaseConfig* instance = nullptr;

        call_once(initFlag, []() {
            instance = new DatabaseConfig();
        });

        return *instance;
    }

    void showConfig() {
        cout << "Server: " << serverAddress 
             << ":" << port << endl;
    }
};

// 多线程测试函数
void threadTask(int id) {
    this_thread::sleep_for(chrono::milliseconds(100 * id));
    auto& config = DatabaseConfig::getInstance();
    
    cout << "线程" << id << "获取配置:";
    config.showConfig();
}

int main() {
    vector<thread> threads;

    // 创建10个线程竞争访问
    for(int i = 0; i < 10; ++i) {
        threads.emplace_back(threadTask, i);
    }

    // 等待所有线程完成
    for(auto& t : threads) {
        t.join();
    }
   system("pause");
    return 0;
}
  • 合理使用 call_once 可以让多线程代码更简洁、更安全,尤其适合需要一次性初始化的场景

7 condition_variable

7.1 生产者-消费者模式概述

生产者-消费者模式是多线程编程中经典的同步问题,需要满足以下条件:

  1. 生产者线程生成数据并放入共享缓冲区。
  2. 消费者线程从缓冲区取出数据并处理。
  3. 同步要求
    • 缓冲区满时,生产者等待消费者消费数据。
    • 缓冲区空时,消费者等待生产者生产数据。

7.2 核心组件

  1. 共享缓冲区:通常使用队列(std::queue)实现。
  2. 互斥锁(std::mutex:保护对缓冲区的并发访问。
  3. 条件变量(std::condition_variable
    • not_full:生产者等待缓冲区非满。
    • not_empty:消费者等待缓冲区非空。

7.3实现代码

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

using namespace std;

const int BUFFER_SIZE = 5;       // 缓冲区容量
queue<int> buffer;               // 共享缓冲区
mutex mtx;                       // 互斥锁
condition_variable not_full;     // 缓冲区非满条件
condition_variable not_empty;    // 缓冲区非空条件

// 生产者函数
void producer(int id) {
    for (int i = 0; i < 10; ++i) {
        unique_lock<mutex> lock(mtx);
        // 如果缓冲区满,等待消费者消费
        not_full.wait(lock, [] { 
            return buffer.size() < BUFFER_SIZE; 
        });
        // 生产数据
        int data = id * 100 + i;
        buffer.push(data);
        cout << "生产者 " << id << " 生产数据: " << data << endl;
        lock.unlock();
        not_empty.notify_one();  // 通知消费者
        this_thread::sleep_for(chrono::milliseconds(100));
    }
}

// 消费者函数
void consumer(int id) {
    for (int i = 0; i < 10; ++i) {
        unique_lock<mutex> lock(mtx);

        // 如果缓冲区空,等待生产者生产
        not_empty.wait(lock, [] { 
            return !buffer.empty(); 
        });

        // 消费数据
        int data = buffer.front();
        buffer.pop();
        cout << "消费者 " << id << " 消费数据: " << data << endl;

        lock.unlock();
        not_full.notify_one();   // 通知生产者

        this_thread::sleep_for(chrono::milliseconds(200));
    }
}

int main() {
    thread producers[2];
    thread consumers[3];

    // 启动2个生产者线程
    for (int i = 0; i < 2; ++i) {
        producers[i] = thread(producer, i);
    }

    // 启动3个消费者线程
    for (int i = 0; i < 3; ++i) {
        consumers[i] = thread(consumer, i);
    }

    // 等待所有线程结束
    for (auto& t : producers) t.join();
    for (auto& t : consumers) t.join();

    return 0;
}

7.4 代码解析

  1. 共享资源保护

    • 所有对缓冲区的操作(pushpop)均在互斥锁mtx的保护下进行。
    • 使用unique_lock自动管理锁的生命周期。
  2. 条件变量的使用

    • 生产者等待条件not_full.wait(lock, predicate)
      当缓冲区满时(buffer.size() >= BUFFER_SIZE),生产者线程阻塞,直到消费者消费数据后通过not_full.notify_one()唤醒。
    • 消费者等待条件not_empty.wait(lock, predicate)
      当缓冲区空时(buffer.empty()),消费者线程阻塞,直到生产者生产数据后通过not_empty.notify_one()唤醒。
  3. 通知机制

    • 生产者生产数据后调用not_empty.notify_one(),唤醒一个等待的消费者。
    • 消费者消费数据后调用not_full.notify_one(),唤醒一个等待的生产者。

7.5 运行结果示例

生产者 0 生产数据: 0
消费者 0 消费数据: 0
生产者 1 生产数据: 100
消费者 1 消费数据: 100
生产者 0 生产数据: 1
消费者 2 消费数据: 1
...
(输出将展示生产与消费的交替过程)

7.6 关键点总结

  1. 防止虚假唤醒
    条件变量的wait必须配合谓词(如buffer.size() < BUFFER_SIZE)使用,确保即使被意外唤醒也能重新检查条件。

  2. 资源管理

    • unique_lockwait时自动释放锁,唤醒后重新获取锁。
    • 使用notify_one而非notify_all,减少不必要的线程竞争。
  3. 死锁避免

    • 确保在调用notify_one前释放锁(通过lock.unlock())。
    • 避免在持有锁时进行耗时操作(如示例中的sleep_for在锁外执行)。

7.7 扩展场景

  • 多生产者和多消费者
    当前代码已支持多个生产者和消费者,通过调整线程数量即可验证。

  • 动态缓冲区大小
    可将BUFFER_SIZE设为动态值,根据需求调整。

  • 复杂数据类型
    queue<int>替换为自定义数据类型队列,实现更复杂的生产-消费逻辑。

此实现完整展示了如何利用condition_variable实现线程安全的生产者-消费者模式,可直接用于实际项目中的任务队列、线程池等场景。

8 跨平台线程池

#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>

class ThreadPool {
public:
    // 构造函数,启动指定数量的工作线程
    ThreadPool(size_t threads = std::thread::hardware_concurrency())
        : stop(false) {
        for(size_t i = 0; i < threads; ++i)
            workers.emplace_back([this] {
                for(;;) {
                    std::function<void()> task;

                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock,
                            [this]{ return this->stop || !this->tasks.empty(); });
                        
                        if(this->stop && this->tasks.empty())
                            return;
                        
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }

                    task();
                }
            });
    }

    // 将任务添加到任务队列,返回一个future以便获取结果
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type> {
        using return_type = typename std::result_of<F(Args...)>::type;

        auto task = std::make_shared< std::packaged_task<return_type()> >(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        
        std::future<return_type> res = task->get_future();
        {
            std::unique_lock<std::mutex> lock(queue_mutex);

            // 不允许在停止线程池后添加新任务
            if(stop)
                throw std::runtime_error("enqueue on stopped ThreadPool");

            tasks.emplace([task](){ (*task)(); });
        }
        condition.notify_one();
        return res;
    }

    // 析构函数,等待所有任务完成并停止所有线程
    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for(std::thread &worker : workers)
            worker.join();
    }

private:
    std::vector<std::thread> workers;     // 工作线程集合
    std::queue<std::function<void()>> tasks; // 任务队列
    
    std::mutex queue_mutex;               // 任务队列互斥锁
    std::condition_variable condition;    // 条件变量
    bool stop;                            // 停止标志
};

// 使用示例
int main() {
    ThreadPool pool(4); // 创建4个工作线程

    // 提交多个任务到线程池
    std::vector<std::future<int>> results;

    for(int i = 0; i < 8; ++i) {
        results.emplace_back(
            pool.enqueue([i] {
                std::this_thread::sleep_for(std::chrono::seconds(1));
                return i*i;
            })
        );
    }

    // 获取任务结果
    for(auto && result : results)
        std::cout << result.get() << ' ';
    std::cout << std::endl;

    return 0;
}

9 异步并发 async future packaged task promise

#include <iostream>
#include <future>
#include <thread>
#include <chrono>
#include <vector>

int compute(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
    return x * x;
}

int main() {
    // 使用 std::async 启动多个异步任务
    std::vector<std::future<int>> futures;
    for (int i = 1; i <= 5; ++i) {
        futures.push_back(std::async(std::launch::async, compute, i));
    }

    // 获取所有任务的结果
    for (auto& future : futures) {
        std::cout << "Result: " << future.get() << std::endl;
    }

    // 使用 std::packaged_task 手动控制任务执行
    std::packaged_task<int(int)> task(compute);
    std::future<int> future = task.get_future();

    std::thread t(std::move(task), 10);
    t.join(); // 等待线程完成

    std::cout << "Packaged Task Result: " << future.get() << std::endl;

    return 0;
}

10 原子操作atomic

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;

atomic<int> a(0); // 使用 atomic<int> 替代普通 int

void func1() {
    for (int i = 0; i < 10000; i++) {
        a++; // 原子操作,无需额外的互斥锁
    }
}

int main() {
    thread t1(func1);
    t1.join();
    cout << a << endl; // 输出最终结果
    system("pause");
    return 0;
}

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

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

相关文章

Java线程认识和Object的一些方法ObjectMonitor

专栏系列文章地址&#xff1a;https://blog.csdn.net/qq_26437925/article/details/145290162 本文目标&#xff1a; 要对Java线程有整体了解&#xff0c;深入认识到里面的一些方法和Object对象方法的区别。认识到Java对象的ObjectMonitor&#xff0c;这有助于后面的Synchron…

pytorch实现长短期记忆网络 (LSTM)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 LSTM 通过 记忆单元&#xff08;cell&#xff09; 和 三个门控机制&#xff08;遗忘门、输入门、输出门&#xff09;来控制信息流&#xff1a; 记忆单元&#xff08;Cell State&#xff09; 负责存储长期信息&…

Games104——引擎工具链高级概念与应用

世界编辑器 其实是一个平台&#xff08;hub&#xff09;&#xff0c;集合了所有能够制作地形世界的逻辑 editor viewport&#xff1a;可以说是游戏引擎的特殊视角&#xff0c;会有部分editor only的代码&#xff08;不小心开放就会变成外挂入口&#xff09;Editable Object&…

消息队列应用示例MessageQueues-STM32CubeMX-FreeRTOS《嵌入式系统设计》P343-P347

消息队列 使用信号量、事件标志组和线标志进行任务同步时&#xff0c;只能提供同步的时刻信息&#xff0c;无法在任务之间进行数据传输。要实现任务间的数据传输&#xff0c;一般使用两种方式&#xff1a; 1. 全局变量 在 RTOS 中使用全局变量时&#xff0c;必须保证每个任务…

网络攻防实战指北专栏讲解大纲与网络安全法

专栏 本专栏为网络攻防实战指北&#xff0c;大纲如下所示 进度&#xff1a;目前已更完准备篇、HTML基础 计划&#xff1a;所谓基础不牢&#xff0c;地动山摇。所以下一步将持续更新基础篇内容 讲解信息安全时&#xff0c;结合《中华人民共和国网络安全法》&#xff08;以下简…

Spark的基本概念

个人博客地址&#xff1a;Spark的基本概念 | 一张假钞的真实世界 编程接口 RDD&#xff1a;弹性分布式数据集&#xff08;Resilient Distributed Dataset &#xff09;。Spark2.0之前的编程接口。Spark2.0之后以不再推荐使用&#xff0c;而是被Dataset替代。Dataset&#xff…

效用曲线的三个实例

效用曲线的三个实例 文章目录 效用曲线的三个实例什么是效用曲线风险与回报&#xff1a;投资决策消费选择&#xff1a;价格与质量的平衡程序员绩效评估&#xff1a;准时与程序正确性 分析- 风险与回报&#xff1a;投资决策分析- 消费选择&#xff1a;价格与质量的平衡- 程序员绩…

neo4j-community-5.26.0 create new database

1.edit neo4j.conf 把 # The name of the default database initial.dbms.default_databasehonglouneo4j # 写上自己的数据库名称 和 # Name of the service #5.0 server.windows_service_nameneo4j #4.0 dbms.default_databaseneo4j #dbms.default_databaseneo4jwind serve…

pytorch实现门控循环单元 (GRU)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 特性GRULSTM计算效率更快&#xff0c;参数更少相对较慢&#xff0c;参数更多结构复杂度只有两个门&#xff08;更新门和重置门&#xff09;三个门&#xff08;输入门、遗忘门、输出门&#xff09;处理长时依赖一般适…

有没有个性化的UML图例

绿萝小绿萝 (53****338) 2012-05-10 11:55:45 各位大虾&#xff0c;有没有个性化的UML图例 绿萝小绿萝 (53****338) 2012-05-10 11:56:03 例如部署图或时序图的图例 潘加宇 (35***47) 2012-05-10 12:24:31 "个性化"指的是&#xff1f; 你的意思使用你自己的图标&…

Vue3.0实战:大数据平台可视化

文章目录 创建vue3.0项目项目初始化项目分辨率响应式设置项目顶部信息条创建页面主体创建全局引入echarts和axios后台接口创建express销售总量图实现完整项目下载项目任何问题都可在评论区,或者直接私信即可。 创建vue3.0项目 创建项目: vue create vueecharts选择第三项:…

洛谷 P1130 红牌 C语言

题目描述 某地临时居民想获得长期居住权就必须申请拿到红牌。获得红牌的过程是相当复杂&#xff0c;一共包括 N 个步骤。每一步骤都由政府的某个工作人员负责检查你所提交的材料是否符合条件。为了加快进程&#xff0c;每一步政府都派了 M 个工作人员来检查材料。不幸的是&…

语音识别播报人工智能分类垃圾桶(论文+源码)

2.1 需求分析 本次语音识别播报人工智能分类垃圾桶&#xff0c;设计功能要求如下∶ 1、具有四种垃圾桶&#xff0c;分别为用来回收厨余垃圾&#xff0c;有害垃圾&#xff0c;可回收垃圾&#xff0c;其他垃圾。 2、当用户语音说出“旧报纸”&#xff0c;“剩菜”等特定词语时…

MVC、MVP和MVVM模式

MVC模式中&#xff0c;视图和模型之间直接交互&#xff0c;而MVP模式下&#xff0c;视图与模型通过Presenter进行通信&#xff0c;MVVM则采用双向绑定&#xff0c;减少手动同步视图和模型的工作。每种模式都有其优缺点&#xff0c;适合不同规模和类型的项目。 ### MVVM 与 MVP…

shiro学习五:使用springboot整合shiro。在前面学习四的基础上,增加shiro的缓存机制,源码讲解:认证缓存、授权缓存。

文章目录 前言1. 直接上代码最后在讲解1.1 新增的pom依赖1.2 RedisCache.java1.3 RedisCacheManager.java1.4 jwt的三个类1.5 ShiroConfig.java新增Bean 2. 源码讲解。2.1 shiro 缓存的代码流程。2.2 缓存流程2.2.1 认证和授权简述2.2.2 AuthenticatingRealm.getAuthentication…

属性编程与权限编程

问题 如何获取文件的大小&#xff0c;时间戳以及类型等信息&#xff1f; 再论 inode 文件的物理载体是硬盘&#xff0c;硬盘的最小存储单元是扇区 (每个扇区 512 字节) 文件系统以 块 为单位(每个块 8 个扇区) 管理文件数据 文件元信息 (创建者、创建日期、文件大小&#x…

用 HTML、CSS 和 JavaScript 实现抽奖转盘效果

顺序抽奖 前言 这段代码实现了一个简单的抽奖转盘效果。页面上有一个九宫格布局的抽奖区域&#xff0c;周围八个格子分别放置了不同的奖品名称&#xff0c;中间是一个 “开始抽奖” 的按钮。点击按钮后&#xff0c;抽奖区域的格子会快速滚动&#xff0c;颜色不断变化&#xf…

R语言绘制有向无环图(DAG)

有向无环图&#xff08;Directed Acyclic Graph&#xff0c;简称DAG&#xff09;是一种特殊的有向图&#xff0c;它由一系列顶点和有方向的边组成&#xff0c;其中不存在任何环路。这意味着从任一顶点出发&#xff0c;沿着箭头方向移动&#xff0c;你永远无法回到起始点。 从流…

Spring Web MVC基础第一篇

目录 1.什么是Spring Web MVC&#xff1f; 2.创建Spring Web MVC项目 3.注解使用 3.1RequestMapping&#xff08;路由映射&#xff09; 3.2一般参数传递 3.3RequestParam&#xff08;参数重命名&#xff09; 3.4RequestBody&#xff08;传递JSON数据&#xff09; 3.5Pa…

129.求根节点到叶节点数字之和(遍历思想)

Problem: 129.求根节点到叶节点数字之和 文章目录 题目描述思路复杂度Code 题目描述 思路 遍历思想(利用二叉树的先序遍历) 直接利用二叉树的先序遍历&#xff0c;将遍历过程中的节点值先利用字符串拼接起来遇到根节点时再转为数字并累加起来&#xff0c;在归的过程中&#xf…