C#与C++交互开发系列(十六):使用多线程

在这里插入图片描述

前言

在开发需要高性能的应用程序时,多线程是提升处理效率和响应速度的关键技术。C++ 和 C# 各自拥有不同的线程模型和并发工具。在跨语言开发中,如何有效地利用两者的并发特性,同时确保线程安全和数据一致性,是一个值得探讨的问题。

本文将介绍几种在 C# 与 C++ 跨语言调用场景下处理多线程的常见方式,包括基本线程创建与管理、线程同步、线程池的使用以及多线程数据共享的注意事项。

1. 基础线程创建与管理

在 C++ 和 C# 中,创建线程的方式略有不同。C++ 可以使用 std::thread,而 C# 使用 System.Threading.Thread 类或 Task 类。通过 P/Invoke 机制,我们可以在 C# 中调用 C++ 的线程函数。

C++ 中的线程创建

C++ 标准库提供了 std::thread 来创建和管理线程。下方示例展示了一个简单的线程创建方法,线程执行一个模拟的计算任务。

// CppLibrary.cpp
#include <thread>
#include <iostream>extern "C" __declspec(dllexport)
void StartCppThread() {std::thread cppThread([]() {for (int i = 0; i < 5; ++i) {std::cout << "Cpp Thread: Iteration " << i ;std::this_thread::sleep_for(std::chrono::seconds(1));std::thread::id this_id = std::this_thread::get_id();std::cout << " C++ 当前线程 ID: " << this_id << std::endl;// 输出线程 ID}});cppThread.detach();
}

C# 中调用 C++ 的线程函数

C# 使用 DllImport 调用 C++ 中的 StartCppThread 函数,C# 本身的主线程可以继续运行,而 C++ 线程在后台执行。

using System;
using System.Runtime.InteropServices;class Program
{[DllImport("MyNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]private static extern void StartCppThread();static void Main(){Console.WriteLine("Starting C++ thread...");StartCppThread();Console.WriteLine($"C# main thread continues...当前线程 ID: {Thread.CurrentThread.ManagedThreadId}");Console.ReadLine();  // 保持应用运行,观察 C++ 线程的输出}
}

执行结果

Starting C++ thread...
C# main thread continues...当前线程 ID: 1
Cpp Thread: Iteration 0 C++ 当前线程 ID: 27388
Cpp Thread: Iteration 1 C++ 当前线程 ID: 27388
Cpp Thread: Iteration 2 C++ 当前线程 ID: 27388
Cpp Thread: Iteration 3 C++ 当前线程 ID: 27388
Cpp Thread: Iteration 4 C++ 当前线程 ID: 27388

2. 线程同步与互斥

当多个线程共享资源时,必须确保线程安全。C++ 中常用 std::mutex,而 C# 使用 lock 关键字或 Mutex 类。

C++ 中的互斥锁

使用 std::mutex 锁定共享资源,确保只有一个线程可以同时访问它。

#include <mutex>
#include <iostream>std::mutex mtx;extern "C" __declspec(dllexport)
void SafeIncrementLock(int* sharedCounter) {std::lock_guard<std::mutex> lock(mtx);(*sharedCounter)++;std::cout << "Counter incremented to " << *sharedCounter ;std::thread::id this_id = std::this_thread::get_id();std::cout << " C++ 当前线程 ID: " << this_id << std::endl;// 输出线程 ID
}

C# 调用线程安全的 C++ 函数

在 C# 中,定义一个共享计数器,并通过调用 SafeIncrement 观察线程同步效果。

using System;
using System.Runtime.InteropServices;
using System.Threading;class Program
{[DllImport("MyNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]private static extern void SafeIncrementLock(ref int sharedCounter);static void Main(){int counter = 0;Thread[] threads = new Thread[5];for (int i = 0; i < threads.Length; i++){threads[i] = new Thread(() =>
{SafeIncrementLock(ref counter);Console.WriteLine($"C# 当前线程 ID: {Thread.CurrentThread.ManagedThreadId}");
});threads[i].Start();}foreach (var thread in threads){thread.Join();}Console.WriteLine($"Final counter value: {counter}");}
}

执行结果

Counter incremented to 1 C++ 当前线程 ID: 36572
Counter incremented to 2 C++ 当前线程 ID: 19000
Counter incremented to 3 C++ 当前线程 ID: 10668
Counter incremented to 4 C++ 当前线程 ID: 11096
Counter incremented to 5 C++ 当前线程 ID: 8016
C# 当前线程 ID: 15
C# 当前线程 ID: 12
C# 当前线程 ID: 14
C# 当前线程 ID: 13
C# 当前线程 ID: 16
Final counter value: 5

3. 使用线程池提高性能

C++11 提供了 std::async,而 C# 中有 TaskThreadPool,两者都能帮助减少线程管理的复杂性,并提高性能。

C++ 中的 std::async

std::async 创建一个后台任务并返回一个 std::future,可以异步执行任务并在需要时获取结果。

#include <future>
#include <iostream>extern "C" __declspec(dllexport)
int LongRunningTask() {std::thread::id this_id = std::this_thread::get_id();std::cout << "C++ 当前线程 ID: " << this_id << std::endl;// 输出线程 IDstd::this_thread::sleep_for(std::chrono::seconds(2));return 42;
}extern "C" __declspec(dllexport)
int RunAsyncTask() {std::future<int> result = std::async(std::launch::async, LongRunningTask);return result.get();  // 等待任务完成并返回结果
}

C# 中的异步调用

C# 中可以使用 Task.Run 并结合 await 进行异步操作。

using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;class Program
{[DllImport("MyNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]private static extern int RunAsyncTask();static async Task Main()
{Console.WriteLine("Starting long-running task...");int result = await Task.Run(() =>{Console.WriteLine($"C# 当前线程 ID: {Thread.CurrentThread.ManagedThreadId}");return RunAsyncTask();});Console.WriteLine($"Task completed with result: {result}");
}
}

执行结果

Starting long-running task...
C# 当前线程 ID: 11
C++ 当前线程 ID: 36016
Task completed with result: 42

4. 线程间的数据共享与传递

在跨语言调用中,若多个线程间需要共享数据,需要特别注意数据的一致性和生命周期管理。可以使用共享内存或传递数据的方式来实现。

使用共享内存

C++ 和 C# 都可以使用共享内存进行数据共享。以下示例使用全局变量模拟共享数据的传递。

// CppLibrary.cpp
#include <atomic>std::atomic<int> sharedData(0);extern "C" __declspec(dllexport)
void SetSharedData(int value) {std::thread::id this_id = std::this_thread::get_id();std::cout << "C++ 当前线程 ID: " << this_id << std::endl;// 输出线程 IDsharedData.store(value);
}extern "C" __declspec(dllexport)
int GetSharedData() {std::thread::id this_id = std::this_thread::get_id();std::cout << "C++ 当前线程 ID: " << this_id << std::endl;// 输出线程 IDreturn sharedData.load();
}

在 C# 中,可以使用 SetSharedDataGetSharedData 方法实现数据的读写共享。

using System;
using System.Runtime.InteropServices;class Program
{[DllImport("MyNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]private static extern void SetSharedData(int value);[DllImport("MyNativeLib.dll", CallingConvention = CallingConvention.Cdecl)]private static extern int GetSharedData();static void Main(){SetSharedData(10);int data = GetSharedData();Console.WriteLine($"Shared data: {data}");}
}

执行结果

C++ 当前线程 ID: 18752
C++ 当前线程 ID: 18752
C# 当前线程 ID: 1
Shared data: 10

5. 注意事项与最佳实践

  • 线程安全:使用互斥锁、原子变量或其他同步机制确保多线程共享数据时的安全性。
  • 避免阻塞主线程:在 C# 中,可使用 Taskasync/await 异步编程,避免阻塞 UI 或主线程。
  • 跨语言生命周期管理:注意管理对象的生命周期,特别是在 C++ 中动态分配的内存需要在适当的时间释放。
  • 性能优化:合理使用线程池,避免频繁创建和销毁线程以节省资源。

总结

本文介绍了在 C++ 与 C# 交互开发中多线程与并发编程的基本概念,包括如何创建和管理线程、实现线程同步与数据共享,并利用异步和线程池提高程序性能。在跨语言开发时,合理地利用两种语言的并发工具,并确保线程安全和数据一致性,是构建高效稳定的多线程程序的关键。

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

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

相关文章

构建最新的LLaMA-Factory镜像

保持最新的仓库代码: git clone https://github.com/hiyouga/LLaMA-Factory.gitcd /root/xiedong/LLaMA-Factory如果不在意本地文件的修改,可以通过以下命令直接获取远端最新的文件: git fetch --all git reset --hard origin/main构建镜像: docker build --progress=pl…

SCSS在Vue中的用法

SCSS在Vue中的用法 一、安装相关依赖1、安装sass - loader和node - sass&#xff08;或dart - sass&#xff09; 二、在组件中使用SCSS1、单文件组件&#xff08;.vue&#xff09;中的样式使用2、**全局样式使用SCSS**3、在组件中使用变量和混入&#xff08;Mixins&#xff09;…

libavdevice.so.58: cannot open shared object file: No such file ordirectory踩坑

博主是将大图切分成小图时遇到 问题一、linux编译后&#xff0c;找不到ffmpeg中的一个文件 产生原因&#xff0c;各种包集成&#xff0c;然后安装以后乱七八糟&#xff0c;甚至官方的教程也不规范导致没有添加路径到系统文件导致系统执行的时候找不到 1.下载 博主进行的离线…

GraphQL系列 - 第1讲 GraphQL语法入门

目录 一、介绍GraphQL二、GraphQL基本使用方法三、Schema 定义语言 (SDL)3.1 类型定义1&#xff09;对象类型2&#xff09;标量类型3&#xff09;枚举类型4&#xff09;输入类型5&#xff09;列表类型6&#xff09;非空类型7&#xff09;接口类型8&#xff09;联合类型 3.2 查询…

thrift idl 语言基础学习

include,他的作用是用作 idl 的模块化编程的 include “include_test.thrift” 单行注释 // 单行注释 /** 多行注释 **/// 指定生成好的代码包,其中 namespace 是固定的,后面的是语言,可以是Java 也可以是其他的,后面的是包路径 namespace java com.rpc.thrift namesp…

查询使用方法:

模糊查询&#xff1a; 查询某一列中内容为空的记录。 -- 模糊查询 (项目中尽量不要使用) -- 张x SELECT * FROM student WHERE student_name LIKE 张_; -- % 不限长度的字符 -- 手机号中有 23 0或多个 SELECT * FROM student WHERE phone LIKE %23% -- 名字包含铮的学生 SELEC…

数据结构与算法分析——你真的理解查找算法吗——基于散列的查找(代码详解+万字长文)

一、算法描述 前面讨论的查找算法在处理小数据量(顺序查找)或者有序的数据集合(二分查找)时才使用。我们需要更加强大的算法能够查找较大的集合,而且并不需要有序。最常使用的一个方法是使用散列函数来将目标元素的一个或者多个特征转换成一个值,这个值用来索引一个已经索引的…

密码管理工具实现

该文档详细描述了实现一个简单的密码管理工具的过程&#xff0c;工具基于PHP和MySQL构建&#xff0c;支持用户注册、密码存储、管理以及角色权限控制等核心功能。 系统架构设计 技术栈&#xff1a;PHP&#xff08;后端逻辑&#xff09;、MySQL&#xff08;数据存储&#xff09…

深度学习(七)深度强化学习:融合创新的智能之路(7/10)

一、深度强化学习的崛起 深度强化学习在人工智能领域的重要地位 深度强化学习作为一种融合了深度学习和强化学习的新技术&#xff0c;在人工智能领域占据着至关重要的地位。它结合了深度学习强大的感知能力和强化学习优秀的决策能力&#xff0c;能够处理复杂的任务和环境。例如…

基于 Java 的 Spring Boot 和 Vue 的宠物领养系统设计与实现

需要代码 vx&#xff1a;Java980320 不收取任何费用 在这个宠物领养系统中&#xff0c;我们可以设定两个角色&#xff1a;管理员和普通用户。每个角色的功能和目标略有不同&#xff0c;以下分别介绍&#xff1a; 管理员 管理员的主要职责是确保平台的高效运行&#xff0c…

PythonBase01

将理论&#xff0c;代码&#xff0c;图示三合一。 day1计算机基础结构 硬件 1944年&#xff0c;美籍匈牙利数学家冯诺依曼提出计算机基本结构。 五大组成部分&#xff1a;运算器、控制器、存储器、输入设备、输出设备。 – 运算器&#xff1a;按照程序中的指令&#xff0c…

C++中如何获取时间并格式化为字符串?

在C中&#xff0c;你可以使用标准库中的 <chrono> 和 <iomanip> 头文件来获取当前时间并将其格式化为字符串。以下是一个简单的示例&#xff0c;展示了如何获取当前时间并将其格式化为一个可读的字符串&#xff08;例如&#xff1a;YYYY-MM-DD HH:MM:SS&#xff09…

APISQL企业版离线部署教程

针对政务、国企、医院、军工等内网物理隔离的客户&#xff0c;有时需要多次摆渡才能到达要安装软件的服务器。本教程将指导您使用Linux和Docker Compose编排服务&#xff0c;实现APISQL的离线部署。 准备 准备一台Linux(x86_64)服务器。 安装Docker Engine&#xff08;推荐版本…

Nginx 的讲解和案例示范

一、基础理解 1.1 Nginx 是什么&#xff1f; Nginx是一个高性能的 Web 服务器和反向代理服务器&#xff0c;同时也可以作为邮件代理服务器。Nginx 以其高并发处理能力、低内存消耗和丰富的功能受到广泛欢迎。 主要功能&#xff1a; 静态资源服务&#xff1a;高效地提供 HTM…

【01初识】-初识 RabbitMQ

目录 学习背景1- 初识 MQ1-1 同步调用什么是同步调用&#xff1f;小结&#xff1a;同步调用优缺点 1-2 异步调用什么是异步调用&#xff1f;小结&#xff1a;异步调用的优缺点&#xff0c;什么时候使用异步调用&#xff1f; 1-3 MQ 技术选型 学习背景 异步通讯的特点&#xff…

为什么使用Node.js爬虫更优

在现代网络开发中&#xff0c;爬虫技术已成为开发者不可或缺的工具。 在比较Node.js和Python在爬虫工具内存使用方面&#xff0c;我们可以从几个关键点来进行分析&#xff1a; 1. 异步I/O和事件驱动模型 Node.js是基于事件驱动和非阻塞I/O模型的&#xff0c;这使得它在处理高…

记录一次mmpretrain训练数据并转onnx推理

目录 1.前言 2.代码 3.数据形态【分类用】 4.配置文件 5.训练 6.测试-分析-混淆矩阵等等&#xff0c;测试图片效果等 7.导出onnx 8.onnx推理 9.docker环境简单补充 1.前言 好久没有做图像分类了&#xff0c;于是想用商汤的mmclassification快速搞一波&#xff0c;发现已…

【微服务】Nacos 注册中心

<!-- nacos 依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${nacos.version}</version><type>pom</type><scope>import&l…

太速科技-430-基于RFSOC的8路5G ADC和8路10G的DAC PCIe卡

430-基于RFSOC的8路5G ADC和8路10G的DAC PCIe卡 一、板卡概述 板卡使用Xilinx的第三代RFSOC系列&#xff0c;单颗芯片包含8路ADC和DAC&#xff0c;64-bit Cortex A53系列4核CPU&#xff0c;Cortex-R5F实时处理核&#xff0c;以及大容量FPGA。 对主机接口采用PCIe Gen3x…

大文件秒传,分片上传,断点续传

大文件分片上传 一 功能描述 1.文件通过web端分片多线程上传到服务端&#xff0c;然后web端发起分片合并&#xff0c;完成大文件分片上传功能 2.上传过的大文件&#xff0c;实现秒传 3.上传过程中&#xff0c;服务异常退出&#xff0c;实现断点续传 二 流程图 三 代码运行…