问题描述
从解释器运行时,以下示例代码按预期运行。但是,当我对示例进行 cythonize 处理然后导入已编译的模块时,我遇到了死锁。
您知道这种行为的原因是什么吗?我可以以某种方式解决吗?是否可以实现超时,以便线程在一定时间后终止?
from concurrent import futures
import time
class StopFlag:
def __init__(self):
self._started = 0
@property
def started(self):
return self._started
def stop(self):
self._started = 0
def start(self):
self._started = 1
def my_loop(stop_flag):
total_records = 0
print ("Started loop")
# wait until measurement is running actively
while not stop_flag.started:
pass
print ("Started measurement")
# wait until measurement is over
while stop_flag.started:
pass
print ("Measurement over")
stop_flag = StopFlag()
with futures.ThreadPoolExecutor() as t:
t.submit(my_loop,stop_flag)
time.sleep(1)
stop_flag.start()
time.sleep(1)
stop_flag.stop()
解释:
$ py -3.6 treading.py
Started loop
Started measurement
Measurement over
Cythonized:
$ cythonize -ai3 treading.py
Compiling C:\mydir\Trials\treading.py because it changed.
[1/1] Cythonizing C:\mydir\Trials\treading.py
running build_ext
building 'treading' extension
creating C:\mydir\Trials\tmpcu1r5lge\Release
creating C:\mydir\Trials\tmpcu1r5lge\Release\mydir
creating C:\mydir\Trials\tmpcu1r5lge\Release\mydir\Trials
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.28.29333\bin\HostX86\x64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MT "-Ic:\program files (x86)\microsoft visual studio\shared\python36_64\include" "-Ic:\program files (x86)\microsoft visual studio\shared\python36_64\include" "-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.28.29333\ATLMFC\include" "-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.28.29333\include" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.18362.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.18362.0\shared" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.18362.0\um" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.18362.0\winrt" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.18362.0\cppwinrt" /TcC:\mydir\Trials\treading.c /FoC:\mydir\Trials\tmpcu1r5lge\Release\mydir\Trials\treading.obj
treading.c
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.28.29333\bin\HostX86\x64\link.exe /nologo /INCREMENTAL:NO /LTCG /nodefaultlib:libucrt.lib ucrt.lib /DLL /MANIFEST:EMbed,ID=2 /MANIFESTUAC:NO "/LIBPATH:c:\program files (x86)\microsoft visual studio\shared\python36_64\libs" "/LIBPATH:c:\program files (x86)\microsoft visual studio\shared\python36_64\PCbuild\amd64" "/LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.28.29333\ATLMFC\lib\x64" "/LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.28.29333\lib\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.18362.0\ucrt\x64" "/LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.18362.0\um\x64" /EXPORT:PyInit_treading C:\mydir\Trials\tmpcu1r5lge\Release\mydir\Trials\treading.obj /OUT:C:\mydir\Trials\treading.cp36-win_amd64.pyd /IMPLIB:C:\mydir\Trials\tmpcu1r5lge\Release\mydir\Trials\treading.cp36-win_amd64.lib
Bibliothek "C:\mydir\Trials\tmpcu1r5lge\Release\mydir\Trials\treading.cp36-win_amd64.lib" und Objekt "C:\mydir\Trials\tmpcu1r5lge\Release\mydir\Trials\treading.cp36-win_amd64.exp" werden erstellt.
Code wird generiert.
Codegenerierung ist abgeschlossen.
$ py -3.6 -c "import treading;"
Started loop
<< deadlock >>
+38: with futures.ThreadPoolExecutor() as t:
+39: t.submit(my_loop,stop_flag)
__Pyx_GetModuleGlobalName(__pyx_t_2,__pyx_n_s_t); if (unlikely(!__pyx_t_2)) __PYX_ERR(0,39,__pyx_L6_error)
__Pyx_GOTREF(__pyx_t_2);
__pyx_t_3 = __Pyx_PyObject_GetAttrStr(__pyx_t_2,__pyx_n_s_submit); if (unlikely(!__pyx_t_3)) __PYX_ERR(0,__pyx_L6_error)
__Pyx_GOTREF(__pyx_t_3);
__Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;
__Pyx_GetModuleGlobalName(__pyx_t_2,__pyx_n_s_my_loop); if (unlikely(!__pyx_t_2)) __PYX_ERR(0,__pyx_L6_error)
__Pyx_GOTREF(__pyx_t_2);
__Pyx_GetModuleGlobalName(__pyx_t_1,__pyx_n_s_stop_flag); if (unlikely(!__pyx_t_1)) __PYX_ERR(0,__pyx_L6_error)
__Pyx_GOTREF(__pyx_t_1);
__pyx_t_8 = PyTuple_New(2); if (unlikely(!__pyx_t_8)) __PYX_ERR(0,__pyx_L6_error)
__Pyx_GOTREF(__pyx_t_8);
__Pyx_GIVEREF(__pyx_t_2);
PyTuple_SET_ITEM(__pyx_t_8,__pyx_t_2);
__Pyx_GIVEREF(__pyx_t_1);
PyTuple_SET_ITEM(__pyx_t_8,1,__pyx_t_1);
__pyx_t_2 = 0;
__pyx_t_1 = 0;
__pyx_t_1 = __Pyx_PyObject_Call(__pyx_t_3,__pyx_t_8,NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0,__pyx_L6_error)
__Pyx_GOTREF(__pyx_t_1);
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
解决方法
免责声明 - 我对 cython 一无所知,我今天第一次尝试。这个“答案”并没有真正回答问题。
我在 Linux 上测试了这个,问题也存在。代码的行为就像将使用协作多任务处理一样。如果我向 StopFlag.started
添加一个 time.sleep(0)
,或者只打开一个文件(不读取它),那么它就可以工作。作为解决方法可能很有用。
编辑2: 似乎与 GIL 相关。下面的更改有帮助(至少在我的系统上)。
class StopFlag:
@property
def started(self):
+ with nogil:
+ pass
return self._started
所以我猜,运行 my_loop 的线程一直持有 GIL(它在我的系统上使用 100% CPU),而主线程没有获得 GIL 能够真正改变 stop_flag 值。
要检查这一点,第二个更改:
with futures.ThreadPoolExecutor() as t:
t.submit(my_loop,stop_flag)
time.sleep(1)
print("before stop_flag.start")
stop_flag.start()
print("after stop_flag.start")
time.sleep(1)
stop_flag.stop()
我仍然只在终端上:
python -c "import treading;"
Started loop
在t.submit
之后,主线程确实执行了任何其他代码行,并且没有打印before stop_flag.start
。