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

相关推荐
无敌最俊朗@1 分钟前
unity3d————屏幕坐标,GUI坐标,世界坐标的基础注意点
开发语言·学习·unity·c#·游戏引擎
凯子坚持 c2 分钟前
纵然千万数据流逝,唯独vector长存
c++
.net开发17 分钟前
WPF使用Prism框架首页界面
前端·c#·.net·wpf
何曾参静谧37 分钟前
「C/C++」C/C++标准库 之 #include<cstddef> 常用定义和宏
c语言·开发语言·c++
木宇(记得热爱生活)37 分钟前
C++ <string> 标头文件详解
开发语言·c++
你有抖音吗1 小时前
【每日 C/C++ 问题】
开发语言·c++
霁月风2 小时前
设计模式——模板方法模式
c++·设计模式
knighthood20012 小时前
ros中仿真编写launch时robot_state_publisher,output参数
c++·ubuntu·ros
小林熬夜学编程2 小时前
【Linux系统编程】第四十二弹---多线程编程全攻略:涵盖线程创建、异常处理、用途、进程对比及线程控制
linux·服务器·c语言·开发语言·c++
£suPerpanda2 小时前
牛客周赛 Round65 补题DEF
开发语言·数据结构·c++·算法·深度优先·动态规划·图论