如何在Python中并行调用dll函数

5 投票
2 回答
5764 浏览
提问于 2025-04-16 16:15

我有一个用Python写的程序,它使用ctypes来调用一个dll函数,并且我传递了一个指针。这个函数应该持续不断地向那个指针写数据,而我希望我的程序能够循环读取这个指针的内容。大概的结构是这样的:

from ctypes import *
import copy
lib = cdll.lib
pointer = c_char_p(" "*100) #however large a buffer I need
#thread this
lib.dostuff(pointer)
#end thread
while True:
    data = pointer.value
    print data

在我的具体案例中,dostuff()是用C语言写的,它打开一个文件并解码,然后把数据作为流写入一个字符数组中。

问题是,我不能使用Python的常规线程模块,因为线程会持有全局解释器锁(GIL),这可能是因为读取dll被认为是文件输入输出,或者因为dll本身进行文件输入输出。因此,循环在dostuff()完成之前不会运行。造成阻塞的原因是什么(dll调用总是会阻塞吗?),我该如何解决这个问题呢?

编辑: ----------已解决---------------------- 正如samplebias在下面指出的,ctypes会释放GIL锁。我发现我程序中的阻塞问题是因为我在运行一个队列: 代码大概是这样的:

import Queue
from threading import Thread

queue = Queue()

def runthread():
     lib.dostuff(pointer)
     while True:
        queue.put(pointer.value)

thread = Thread(target=runthread)
thread.start()
while True:
    data = queue.get()
    dostuffwithdata(data)

程序之所以阻塞,是因为当队列为空时,queue.get()会一直阻塞,直到有东西放进去!当然,由于我没有单独为dll调用开线程,它在我把指针的结果放入队列之前就已经完成了。解决方案大概是这样的:

import Queue
from threading import Thread

queue = Queue()

def runthread():
     q = Thread(target=lib.dostuff, args=(pointer,))
     q.start()
     while True:
         queue.put(pointer.value)

thread = Thread(target=runthread)
thread.start()
while True:
   data = queue.get()
   dostuffwithdata(data)

希望这对某些人有帮助!

2 个回答

0

Python的 multiprocessing 模块的使用方式和线程(threading)模块很像,但它没有全局解释器锁(GIL)。

9

这确实可以通过线程来实现,因为 ctypes 在调用 C 函数之前会释放全局解释器锁(GIL)。这意味着 C 代码可以在不造成死锁的情况下调用 Python 代码。

你可能遇到的唯一问题是如何告诉 DLL 停止发送数据,不过这也有解决办法,比如可以传递一个第二个指针作为标志,指示何时返回。

下面是一个符合你问题的工作示例,比如说 GIL 被释放,Python 和 C 代码可以同时运行:

共享对象: test.c

#include <stdint.h>
#include <stdio.h>

void
dostuff(uint64_t *ptr)
{
    while (1)
        (*ptr)++;
}

编译它:

% gcc -shared -g -o test.so test.c -fPIC

Python 代码: test.py

import ctypes
import sys
import time
import threading

lib = ctypes.cdll.LoadLibrary('./test.so')
val = ctypes.c_uint64(0)

def loop():
    lib.dostuff(ctypes.byref(val))

t1 = threading.Thread(target=loop)
t1.start()

for i in range(1000):
    sys.stdout.write('%s ' % val.value)
    sys.stdout.flush()
    time.sleep(0.05)

输出

% python test.py 
0 24664442 48388062 71628820 94834416 118004961 141095893 164936784 ... ...

撰写回答