我可以在Python中从线程导入模块吗?

时间:2017-10-11 23:03:58

标签: python multithreading python-3.x

这一切都始于昨晚,当我制作一个需要8个左右的软件包的脚本时,包括pygame.mixer,在我的计算机上导入这个需要几秒钟。

这意味着在脚本开始之前我必须等待10秒左右才能加载所有导入。因为我希望脚本显然尽可能快,所以我可以开始运行脚本,同时通过以下方式获取导入:

import threading


def import_modules():
    import tkinter as tk
    from pygame import mixer
    import json
    import webbrowser
    print('imports finished')

a = threading.Thread(target=import_modules)
a.start()
for i in range(10000):
    print('Getting Modules')

所以我的问题是:

这被认为是不好的做法会导致问题吗?

如果有,我可以使用替代品吗?

或者可以这样做吗?

2 个答案:

答案 0 :(得分:3)

如果您正在使用CPython,这可能不会产生与您期望的一样多的改进。

CPython有一个全局解释器锁(" GIL"),确保一次只有一个线程可以执行Python字节码。

因此,只要导入线程执行Python代码,另一个线程就不会运行。例如,GIL由一个线程释放。等待I / O.因此可以节省一些时间。

对于tkinter是否真正的线程安全存在意见分歧。在原始线程中运行tkinter主循环并且不调用来自其他线程的tkinter调用仍然是明智的,因为可能导致崩溃。

GIL也会导致GUI程序出现问题。如果您使用第二个线程进行长时间运行的计算,则用户界面的响应速度可能会降低。至少有两种可能的解决方案。第一个是将长时间运行的计算分成小块,每个小块由after方法执行。第二种是在不同的进程中运行计算

评论中的后续问题:

  

还有什么可以加快执行时间吗?

您必须做的第一件事是 measure ;什么完全导致问题。然后,您可以查看问题区域并尝试改进它们。

例如模块加载时间。在profiler下运行您的应用,查看模块加载的时间和原因。

如果pygame.mixer加载时间太长,您可以使用平台的原生调音台。类UNIX操作系统通常具有/dev/mixer设备,而ms-windows具有不同的API。使用那些绝对不会花费10秒钟。 与此相关的成本:您将失去操作系统之间的可移植性。

  

有哪些替代方案

使用多核是尝试加快速度的常用策略。目前在CPython上,唯一的通用方式可以让代码在多个内核上并行运行multiprocessingconcurrent.futures

但是,如果这种策略可行,则取决于问题的性质。

如果您的问题涉及对大量数据进行相同的计算,则相对容易并行化。在这种情况下,您可以期望最大加速大致相当于您使用的核心数。

可能是您的问题包含多个步骤,每个步骤都取决于上一步的结果。这些问题本质上是连续的,并且难以并行执行。

可能加快速度的其他方法可能是使用另一个Python实现,如Pypy。或者,您可以将cython与类型提示结合使用,将性能关键部分转换为已编译的C代码。

答案 1 :(得分:1)

我知道这是一个旧线程,但是我正在寻找一种方法来最小化我的应用程序的加载时间,并希望用户看到gui,以便他可以在后台导入其他模块的同时与它进行交互

我已经读到一些答案,暗示了一种懒惰的导入技术,我发现这很复杂“对我来说”,然后我偶然发现了一个建议,使用线程在后台导入模块,然后给了我一个机会,然后发现了它是最适合我需求的主意

下面的

是使用PySimpleGUI的示例gui应用程序的代码,它要求用户输入URL,它将在默认浏览器窗口中打开它,唯一需要这样做的模块是webbrowser ,因此可以在加载其他模块时完成此工作

我在这段代码中添加了注释,以解释大部分内容,希望对您有所帮助, 在python 3.6,windows10上进行了测试。

请注意:这只是一个虚拟代码,作为展示。

# import essentials first
import PySimpleGUI as sg
import time, threading

# global variable names to reference to the imported modules, this way will 
# solve the problem of importing inside a function local namespace
pg = None
js = None
wb = None

progress = 0  # for our progress bar

def importer():
    # we will simulate a time consuming modules by time.sleep()
    global progress
    progress = 10
    start = time.time()
    global pg, js, wb
    import pygame as pg
    time.sleep(3)
    print(f'done importing pygame mixer in {time.time()-start} seconds')
    progress = 40

    start = time.time()
    import webbrowser as wb
    time.sleep(2)
    print(f'done importing webbrowser in {time.time()-start} seconds')
    progress = 70

    start = time.time()
    import json as js
    time.sleep(10)
    print(f'done importing json in {time.time()-start} seconds')

    progress = 100
    print('imports finished')

# start our importer in a separate thread
threading.Thread(target=importer).start()

# main app 
def main():
    # window layout
    layout = [[sg.Text('Enter url:', size=(15,1)), sg.Input(default_text='https://google.com', size=(31, 1), key='url')],
            [sg.Text('Loading modules:', size=(15,1), key='status'), 
            sg.ProgressBar(max_value=100, orientation='horizontal', size=(20,10), key='progress')],
            [sg.Button('Open url', disabled=True, key='open_url'), sg.Button('joysticks', disabled=True, key='joysticks'), sg.Cancel()]]
    window = sg.Window('test application for lazy imports', layout=layout) # our window

    while True:  # main application loop
        event, values = window.Read(timeout=10)  # non blocking read from our gui
        if event in [None, 'Cancel']:
            window.Close()
            break 

        elif event == 'open_url':
            wb.open(values['url'])
            print('helllooooooooooooo')

        elif event == 'joysticks':
            # show joystics number currently connected
            pg.init()
            n = pg.joystick.get_count()  # Get count of joysticks
            sg.Popup(f'joysticks number currently connected to computer = {n}')

        # open url button is disabled by default and will be enabled once our webbrowser module imported
        if wb:
            window.Element('open_url').Update(disabled= False)

        if pg:
            window.Element('joysticks').Update(disabled= False)

        # progress bar
        window.Element('progress').UpdateBar(progress)
        if progress >= 100:
            window.Element('status').Update('Loading completed', background_color='green')

main()