11

I have some simple C-code which uses a single global-variable. Obviously this is not thread-safe, so when I call it from multiple threads in C# using P/invoke, things screw up.

How can I either import this function separately for each thread, or make it thread-safe?

I tried declaring the variable __declspec(thread), but that caused the program to crash. I also tried making a C++/CLI class, but it doesn't allow member-functions to be __declspec(naked), which I need (I'm using inline-assembly). I'm not very experienced writing multi-threaded C++ code, so there might be something I'm missing.


Here is some example code:

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);
    }
}

[Edit]: The result of SomeFunction() must go in some prescribed order based on someGlobalVariable (think of eg. a PRNG, with someGlobalVariable as the internal state). So, using a mutex or other sort of lock is not an option - each thread must have its own copy of someGlobalVariable.

BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
  • How often do you call this - can you do the locking in the C# side, assuming you don't own the C code? – Steve Townsend Apr 30 '12 at 18:06
  • @SteveTownsend: No, mostly all the multiple threads do are call that function. – BlueRaja - Danny Pflughoeft Apr 30 '12 at 18:08
  • 3
    Does it have to be a global variable? You could return the state from the function and require a state be passed back to the function when it's called the next time. (Or a pointer to the state.) – dtb Apr 30 '12 at 18:56
  • @dtb: Hm, that's an interesting idea. The actual state is larger than an `int`, so that would require some copying (and thus might actually be slower than the single-threaded version), but it's something I will try out. Thanks! – BlueRaja - Danny Pflughoeft Apr 30 '12 at 18:59
  • 1
    try to use [MethodImpl(MethodImplOptions.Synchronized)] attribute on SomeFunction – Andriy Vandych Nov 18 '13 at 19:26
  • @AndriyVandych If I was going to do that, I might as well just single-thread the entire computation.. – BlueRaja - Danny Pflughoeft Nov 18 '13 at 19:28
  • 1
    If you don't want to give up the global variable and don't want to synchronize the method calls, then the only option is really to have multiple processes (a global variables exists once per address space). For example, you could start your C# program multiple times and implement a TPL TaskFactory that schedules tasks on this pool of processes. – dtb Nov 19 '13 at 04:09

4 Answers4

8

A common pattern is to have

  • a function that allocates memory for the state,
  • a function that has no side-effects but mutating the passed-in state, and
  • a function that releases the memoy for the state.

The C# side would look like this:

Usage:

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

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

    Console.WriteLine(result);
});

Declarations:

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 magic:

[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;
    }
}
dtb
  • 213,145
  • 36
  • 401
  • 431
2

Personally if the C code was to be called elsewhere I would use a mutex there. If that doesn't float your boat you can lock in .Net quite easily:

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);

[Edit..]

As pointed out, this serializes access to the function which you can't do yourself in this case. You have some C/C++ code that (wrongly IMO) uses a global for state during the call to the exposed function.

As you have observed that the __declspec(thread) trick doesn't work here then I would try to pass your state/context back and forth as an opaque pointer like so:-

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);
    }
}

And the corresponding C# wrapper as before:

[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);
IanNorton
  • 7,145
  • 2
  • 25
  • 28
  • 1
    This would essentially serialize my threads. Isn't there some way to make the global-variable stored in thread-local storage instead? – BlueRaja - Danny Pflughoeft Apr 30 '12 at 18:14
  • Without changing the C code? no. Even if each C# thread can store a local copy some how there is still no firewall vs other threads modifying the C 'master copy'. – Steve Townsend Apr 30 '12 at 18:33
  • @SteveTownsend: I don't mind changing the C code; but, I don't know what to do. As I mentioned, `__declspec(thread)` does not seem to work. I need one copy of the variable for each thread; a mutex or other sort of lock is not an option. I suppose I should have mentioned, the result of `SomeFunction()` must go in some prescribed order based on `someGlobalVariable` *(think of eg. a PRNG, with `someGlobalVariable` as the seed/internal state)* – BlueRaja - Danny Pflughoeft Apr 30 '12 at 18:39
  • then you should be able to do this, with suitable logic for first-time init if there is no value for the current managed thread. Even then this is complicated, because managed threads (C#) and native threads are not 1-1 - see here http://stackoverflow.com/questions/1279950/relationship-between-managedthreadid-and-operating-system-threadid – Steve Townsend Apr 30 '12 at 18:43
  • @Steve: Yes, as I mentioned it's like the internal state of a PRNG - I am not *that* naive :) I don't understand your suggestion though - how does *"with suitable logic for first-time init if there is no value for the current managed thread"* get me a separate instance for each thread? – BlueRaja - Danny Pflughoeft Apr 30 '12 at 18:49
  • Seems to me like your C code needs to correlate 'managed thread ID' with 'current value' - you can't just use the native thread id since it's not 1-1 with managed thread id. – Steve Townsend Apr 30 '12 at 19:08
  • Passing around IntPtrs is not a good idea; it's better to use a SafeHandle. (Feel free to copy the SomeSafeHandle class from my answer.) – dtb Apr 30 '12 at 20:04
2

You can either make sure what you only call _someFunction once at a time in your C# code or change the C code to wrap the access to the global variable in a synchronization primitive like a critical section.

I would recommend changing the C# code rather than the C code, as the C# code is multi-threaded, not the C code.

Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
1

The good news, you can create a __declspec(naked) function as a member of C++ (non-CLI) class:

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

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

The bad news, you will need COM to be able to use such class. That's right: asm wrapped in C++, wrapped in COM, wrapped in RCW, wrapped in CLR...

Pavel Zhuravlev
  • 2,691
  • 1
  • 18
  • 17