如果没有 GIL,您将无法使用 Python dict,因为您可以使用它执行的所有操作都涉及操作 Python 对象。您最明智的选择是接受您需要 GIL。还有一个不太明智的选择涉及 C++ 映射,但可能很难适用于您的具体情况。
您可以使用with gil: 重新获取 GIL。这里有明显的开销(使用 GIL 的部分不能并行执行,它可能会延迟等待 GIL)。但是,如果字典操作是一大段 Cython 代码的一小部分,这可能还不错:
with nogil:
# some large chunk of computationally intensive code goes here
with gil:
# your dictionary code
# more computationally intensive stuff here
另一个不太明智的选择是使用 C++ 映射(以及其他 C++ 标准库数据类型)。 Cython 可以包装这些并自动转换它们。根据您的示例数据举一个简单的例子:
from libcpp.map cimport map
from libcpp.string cimport string
from libcpp.vector cimport vector
from cython.operator cimport dereference, preincrement
def f():
my_dict = {'a':[1,2,3], 'b':[4,5] , 'c':[7,1,2]}
# the following conversion has an computational cost to it
# and must be done with the GIL. Depending on your design
# you might be able to ensure it's only done once so that the
# cost doesn't matter much
cdef map[string,vector[int]] m = my_dict
# cdef statements can't go inside no gil, but much of the work can
cdef map[string,vector[int]].iterator end = m.end()
cdef map[string,vector[int]].iterator it = m.begin()
cdef int total_length = 0
with nogil: # all this stuff can now go inside nogil
while it != end:
total_length += dereference(it).second.size()
preincrement(it)
print total_length
(你需要用language='c++'编译它)。
这样做的明显缺点是必须事先知道 dict 中的数据类型(它不能是任意 Python 对象)。但是,由于您无法在 nogil 块内操作任意 Python 对象,因此无论如何您都受到了很大的限制。
6 年后附录:我不建议将“在所有地方使用 C++ 对象”方法作为一般方法。 Cython-C++ 接口有点笨拙,你可以花很多时间来解决它。 Python 容器实际上比你想象的要好。每个人都倾向于忘记将 C++ 对象转换为 Python 对象的成本。 People rarely consider if they really need to release the GIL or if they just read an article on the internet somewhere saying that the GIL is bad..
这对某些任务有好处,但在盲目地将所有 list 替换为 vector、dict 替换为 map 等之前,请仔细考虑。通常,如果您的 C++ 类型完全存在于您的函数中,它 可能是个不错的举动(但请三思……)。如果它们被转换为输入或输出参数,请考虑第三次。