11

我有一些使用单个全局变量的简单 C 代码。显然这不是线程安全的,所以当我使用 P/invoke 从 C# 中的多个线程调用它时,事情就搞砸了。

如何为每个线程单独导入此函数,或使其成为线程安全的?

我尝试声明变量__declspec(thread),但这导致程序崩溃。我还尝试制作一个 C++/CLI 类,但它不允许成员函数是__declspec(naked)我需要的(我正在使用内联汇编)。我在编写多线程 C++ 代码方面不是很有经验,所以我可能缺少一些东西。


这是一些示例代码:

C#

[DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SomeFunction(int parameter1, int parameter2);

C++

extern "C"
{
    int someGlobalVariable;
    int __declspec(naked) _someFunction(int parameter1, int parameter2)
    {
        __asm
        {
            //someGlobalVariable read/written here
        }
    }
    int __declspec(dllexport) SomeFunction(int parameter1, int parameter2)
    {
        return _someFunction(parameter1, parameter2);
    }
}

[编辑] : 的结果SomeFunction()必须基于某种规定的顺序someGlobalVariable (例如一个 PRNG,以someGlobalVariable作为内部状态)。因此,不能使用互斥锁或其他类型的锁 - 每个线程都必须有自己的someGlobalVariable.

4

4 回答 4

8

一个常见的模式是有

  • 为状态分配内存的函数,
  • 一个没有副作用但会改变传入状态的函数,以及
  • 释放状态内存的函数。

C# 端看起来像这样:

用法:

var state = new ThreadLocal<SomeSafeHandle>(NativeMethods.CreateSomeState);

Parallel.For(0, 100, i =>
{
    var result = NativeMethods.SomeFunction(state.Value, i, 42);

    Console.WriteLine(result);
});

声明:

internal static class NativeMethods
{
    [DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern SomeSafeHandle CreateSomeState();

    [DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int SomeFunction(SomeSafeHandle handle,
                                          int parameter1,
                                          int parameter2);

    [DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)]
    internal static extern int FreeSomeState(IntPtr handle);
}

SafeHandle 魔法:

[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal class SomeSafeHandle : SafeHandle
{
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    public SomeSafeHandle()
        : base(IntPtr.Zero, true)
    {
    }

    public override bool IsInvalid
    {
        get { return this.handle == IntPtr.Zero; }
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    protected override bool ReleaseHandle()
    {
        return NativeMethods.FreeSomeState(this.handle) == 0;
    }
}
于 2012-04-30T19:06:43.960 回答
2

就个人而言,如果要在其他地方调用 C 代码,我会在那里使用互斥锁。如果这不能让你的船浮起来,你可以很容易地锁定.Net:

static object SomeFunctionLock = new Object();

public static int SomeFunction(int parameter1, int parameter2){
  lock ( SomeFunctionLock ){
    return _SomeFunction( parameter1, parameter2 );
  }
}

[DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int _SomeFunction(int parameter1, int parameter2);

[编辑..]

正如所指出的,这会序列化对在这种情况下您无法自己执行的功能的访问。您有一些 C/C++ 代码(错误地 IMO)在调用公开函数期间使用全局状态。

正如您所观察到的那样,该__declspec(thread)技巧在这里不起作用,那么我将尝试将您的状态/上下文作为不透明的指针来回传递,如下所示:-

extern "C" 
{
    int _SomeOtherFunction( void* pctx, int p1, int p2 )
    { 
        return stuff;
    }

    // publically exposed library function
    int __declspec(dllexport) SomeFunction(int parameter1, int parameter2)
    {
        StateContext ctx;
        return _SomeOtherFunction( &ctx, parameter1, parameter2 );
    }

    // another publically exposed library function that takes state
    int __declspec(dllexport) SomeFunctionWithState(StateContext * ctx, int parameter1, int parameter2)
    {
        return _SomeOtherFunction( ctx, parameter1, parameter2 );
    }

    // if you wanted to create/preserve/use the state directly
    StateContext * __declspec(dllexport) GetState(void) {
        ctx = (StateContext*) calloc( 1 , sizeof(StateContext) );
        return ctx;
    }

    // tidy up
    void __declspec(dllexport) FreeState(StateContext * ctx) {
        free (ctx);
    }
}

和之前对应的 C# 包装器:

[DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int SomeFunction(int parameter1, int parameter2);

[DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int SomeFunctionWithState(IntPtr ctx, int parameter1, int parameter2);

[DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr GetState();

[DllImport("MyDll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void FreeState(IntPtr);
于 2012-04-30T18:09:04.583 回答
2

您可以确保在 C# 代码中一次只调用一次 _someFunction,或者更改 C 代码以将对全局变量的访问包装在同步原语中,如临界区。

我建议更改 C# 代码而不是 C 代码,因为 C# 代码是多线程的,而不是 C 代码。

于 2012-04-30T18:10:52.127 回答
1

好消息是,您可以将函数创建__declspec(naked)为 C++(非 CLI)类的成员:

class A {
    int n;
public:
    A() { n = 0; }
    void f(int n1, int n2);
};

__declspec(naked) void A::f(int n1, int n2)
{
    n++;
}

坏消息是,你需要 COM 才能使用这样的类。没错:asm 用 C++ 包装,用 COM 包装,用 RCW 包装,用 CLR 包装……

于 2012-04-30T19:29:01.737 回答