Home > Mobile >  How to call a variadic function with ctypes from multiple threads?
How to call a variadic function with ctypes from multiple threads?

Time:12-01

I have a shared library, libfoo.so, with a variadic function:

int foo(int handle, ...);

that uses handle to access to static variables within the library.

Now, I want to use it with ctypes in a multithread program.

import ctypes as ct

# main
lib = ct.cdll.LoadLibrary('libfoo.so')
foo = lib.foo
foo.restype = ct.c_int

# thread 1 code
def thread1(handle):
    foo.argtypes = [ct.c_int, ct.c_int]
    foo(handle, 2);

# thread 2 code
def thread2(handle):
    foo.argtypes = [ct.c_int, ct.c_double]
    foo(handle, 2.);

The problem is that both threads modify the same foo.argtypes and this leads to conflicts. I cannot load the same library twice because I need to access to static data into the library. Moreover, the foo object, that is an instance of _FuncPtr, is not copyable.

An obvious solution is to add a mutex to protect argtypes while foo is being called. Are there any other solutions to this problem?

CodePudding user response:

Instead of setting .argtypes for each function resulting in a race condition, create the correct ctypes type as you call each function:

import ctypes as ct

# main
lib = ct.cdll.LoadLibrary('libfoo.so')
foo = lib.foo
foo.argtypes = ct.c_int,  # define the known types. ctypes will allow more
foo.restype = ct.c_int

# thread 1 code
def thread1(handle):
    foo(handle, ct.c_int(2))

# thread 2 code
def thread2(handle):
    foo(handle, ct.c_float(2))

Here's a test. The C printf aren't serialized so may mix up two prints in a single line, but the numbers are correct:

test.c

#include <stdio.h>
#include <stdarg.h>

#ifdef _WIN32
#   define API __declspec(dllexport)
#else
#   define API
#endif

API int foo(int handle, ...) {
    va_list valist;
    va_start(valist, handle);
    switch(handle) {
    case 1:
        printf("%d\n", va_arg(valist, int));
        break;
    case 2:
        printf("%f\n", va_arg(valist, float));
        break;
    case 3:
        int x = va_arg(valist, int);
        float y = va_arg(valist, float);
        printf("%d %f\n", x, y);
        break;
    default:
        ;
    }
    va_end(valist);
    return 123;
}

test.py

import ctypes as ct
from threading import Thread

dll = ct.CDLL('./test')
dll.foo.argtypes = ct.c_int,
dll.foo.restype = ct.c_int

def thread1():
    for _ in range(5):
        dll.foo(1, ct.c_int(1));

# thread 2 code
def thread2():
    for _ in range(5):
        dll.foo(2, ct.c_float(2.125))

def thread3():
    for _ in range(5):
        dll.foo(3, ct.c_int(3), ct.c_float(3.375))

threads = [Thread(target=f) for f in (thread1, thread2, thread3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

Output:

1
1
1
2.125000
1
2.125000
3 3.375000
1
2.125000
3 3.375000
2.125000
3 3.375000
2.125000
3 3.375000
3 3.375000
  • Related