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 来创建和管理线程。下方示例展示了一个简单的线程创建方法,线程执行一个模拟的计算任务。

cpp 复制代码
// 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++ 线程在后台执行。

csharp 复制代码
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++ 线程的输出
    }
}

执行结果

bash 复制代码
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 锁定共享资源,确保只有一个线程可以同时访问它。

cpp 复制代码
#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 观察线程同步效果。

csharp 复制代码
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}");
    }
}

执行结果

bash 复制代码
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,可以异步执行任务并在需要时获取结果。

cpp 复制代码
#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;// 输出线程 ID
    std::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 进行异步操作。

csharp 复制代码
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}");
}
}

执行结果

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

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

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

使用共享内存

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

cpp 复制代码
// 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;// 输出线程 ID
    sharedData.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;// 输出线程 ID
    return sharedData.load();
}

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

csharp 复制代码
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}");
    }
}

执行结果

bash 复制代码
C++ 当前线程 ID: 18752
C++ 当前线程 ID: 18752
C# 当前线程 ID: 1
Shared data: 10

5. 注意事项与最佳实践

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

总结

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

相关推荐
萧萧玉树3 分钟前
分布式在线评测系统
前端·c++·后端·负载均衡
xcLeigh14 分钟前
C# Winform贪吃蛇小游戏源码
开发语言·c#
FFDUST18 分钟前
C++ 优先算法 —— 无重复字符的最长子串(滑动窗口)
c语言·c++·算法·leetcode
shiming887919 分钟前
C/C++链接数据库(MySQL)超级详细指南
c语言·数据库·c++
m0_7380545633 分钟前
【leetcode】全排列 回溯法
c++·算法·leetcode·回溯法
ZZZ_O^O1 小时前
【贪心算法第五弹——300.最长递增子序列】
c++·学习·算法·leetcode·贪心算法
.NET快速开发框架1 小时前
一文搞懂flex(弹性盒布局)
c#·.netcore·web前端·开发技术·rdif·rdiframework.net
呆呆小雅1 小时前
C# 结构体
android·java·c#
Koishi_TvT1 小时前
蓝桥杯c++算法秒杀【6】之动态规划【下】(数字三角形、砝码称重(背包问题)、括号序列、异或三角:::非常典型的必刷例题!!!)
c语言·c++·算法·性能优化·蓝桥杯·动态规划·c
孤独且没人爱的纸鹤1 小时前
C++ 二叉搜索树(Binary Search Tree, BST)深度解析与全面指南:从基础概念到高级应用、算法优化及实战案例
c语言·数据结构·c++·算法