rpy2(来自 python 的 R 代码)如何在线程中 运行 而不会阻塞其他线程?

How can rpy2 (R code from python) be run in a thread without blocking other threads?

问题总结

我正在尝试在第二个线程中使用 rpy2 运行 来自 python 的 R 代码(不是 multiprocessing,一个线程就可以),但看起来继续阻塞我的主线程。这可以避免吗?


我的原码:

import time
import threading
import rpy2.robjects as robjects

def long_r_function():
    robjects.r('Sys.sleep(10)')    # pretend we have a long-running function in R

r_thread = threading.Thread(target=long_r_function, daemon=True)
start_time = time.time()
r_thread.start()

while r_thread.is_alive():
    print("R running...")    # expect to be able to see this print from main thread, 
    time.sleep(2)            # while R does work in second thread

print("Finished after ", time.time() - start_time, " seconds")

预期输出:

我希望看到 5 个“R running”打印,因为当 R 休眠 10 秒时主线程不应该被阻塞。

实际输出:

一张我预计 5:

R running...
Finished after  12.006645679473877  seconds

移动 rpy2.objects 导入线程的目标函数:

因为 import rpy2.robjects as robjects 语句已经导致 R 环境被初始化,我认为将它移动到线程的目标函数 long_r_function 中可能会有所帮助,看看是否会以某种方式将它与主线程。 这没有用


正在用 python 代码替换 rpy2 代码:

就像完整性检查一样,如果我完全摆脱 rpy2 代码,而是将线程的目标函数定义为

def long_r_function():
    time.sleep(10)

我正确地得到了预期的输出(现在是谎言,因为没有任何 R 运行ning):

R running...
R running...
R running...
R running...
R running...
Finished after  10.002039194107056  seconds

更新:主线程中的 rpy2 也会阻塞第二个线程

当我用下面的代码切换线程时,rpy2 运行s R 代码在主线程中,我试着看看 python 是否仍然可以做一些事情在第二个线程中,出现同样的问题。

def print_and_sleep():
    while True:
        print("Second thread running...")
        time.sleep(2)

py_thread = threading.Thread(target=print_and_sleep, daemon=True)
py_thread.start()
robjects.r('Sys.sleep(10)')

输出:只有一个或两个 "Second thread running..." 的打印,而不是预期的 5 个。同样,使用与上面类似的健全性检查也可以正常工作,其中 rpy2 仅替换为 python代码。

所以,这不是特定于主线程的。似乎 rpy2 只是阻塞了所有 python 线程,不管它在哪里 运行ning?


版本信息:

rpy2.__version__ = 2.8.5
rpy2.rinterface.R_VERSION_BUILD = ('3', '3.2', '', 71607)
sys.version = 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]

Python 使用锁(称为 GIL)来防止字节码的并发执行,并且在调用 C 时(这是通过 rpy2 执行 R 代码时发生的情况)默认情况下 GIL 不是发布到该代码 returns.

现在可以从 C 扩展中释放 GIL 但是 R 对多线程非常不友好(如 "will segfault really quickly")。因此,实现 GIL 版本并处理可能发生 R 并发评估的所有可能方式的努力并不值得。

如果目的是将 R 代码与 rpy2 并行化,请使用进程而不是线程(例如使用 Python 的 multiprocessing)。

Igautier 的回答解释了为什么这在技术上很困难。实现预期目标的最佳方法似乎是使用 multiprocessing 而不是 threading,即使线程在其他情况下就足够了。代码如下所示:

import time
from multiprocessing import Process

def long_r_function():
    import rpy2.robjects as robjects
    robjects.r('Sys.sleep(10)')    # pretend we have a long-running function in R

if __name__ == '__main__':
    r_process = Process(target=long_r_function)
    start_time = time.time()
    r_process.start()

    while r_process.is_alive():
        print("R running...")    # expect to be able to see this print from main process,
        time.sleep(2)            # while R does work in second process

    print("Finished after ", time.time() - start_time, " seconds")

它给出了预期的输出(由于创建新进程的开销,通常打印 6 或 7 次而不是 5 次):

R running...
R running...
R running...
R running...
R running...
R running...
Finished after  12.413572311401367  seconds