Python 烧瓶里有蟒蛇吗?
我最近正在研究python多线程,我发现GIL将迫使python一次运行一个线程,即使在多核CPU上也是如此 所以我做了一些小手术 我的代码如下:Python 烧瓶里有蟒蛇吗?,python,multithreading,flask,Python,Multithreading,Flask,我最近正在研究python多线程,我发现GIL将迫使python一次运行一个线程,即使在多核CPU上也是如此 所以我做了一些小手术 我的代码如下: import threading import time COUNT = 50000000 def count(): i = 0 print('thread id =', threading.get_ident()) while(i < COUNT): i = i +1 start_time = ti
import threading
import time
COUNT = 50000000
def count():
i = 0
print('thread id =', threading.get_ident())
while(i < COUNT):
i = i +1
start_time = time.time()
count()
count()
end_time = time.time()
print(f'execution time without multiple threading : {end_time - start_time}')
start_time = time.time()
t_1 = threading.Thread(target=count)
t_2 = threading.Thread(target=count)
t_1.start()
t_2.start()
t_1.join()
t_2.join()
end_time = time.time()
print(f'execution time with multiple threading : {end_time - start_time}')
这就是吉尔的工作原理
但现在我在烧瓶上做同样的过程
看来吉尔并没有像预期的那样工作。
这是我的代码:
server.py
from flask import Flask
import threading
import os
app = Flask(__name__)
COUNT = 50000000
@app.route('/')
def hello():
print ('Thread id = ', threading.get_ident())
print ('Process id = ', os.getpid())
i = 0
while(i < COUNT):
i = i +1
return "Hello World!"
if __name__ == '__main__':
app.run()
import requests
import time
import multiprocessing as mp
def send():
start_time = time.time()
r = requests.get('http://localhost:5000')
end_time = time.time()
print(f'response after {end_time - start_time}')
if __name__ == '__main__':
p_1 = mp.Process(target=send)
p_2 = mp.Process(target=send)
p_1.start()
p_2.start()
p_1.join()
p_2.join()
server.py的结果:
Thread id = 18428
Process id = 19000
Thread id = 17436
Process id = 19000
127.0.0.1 - - [14/Sep/2020 09:36:19] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [14/Sep/2020 09:36:19] "GET / HTTP/1.1" 200 -
client.py的结果
response after 7.361770153045654
response after 7.373677015304565
似乎有两个客户同时得到了回复,但对于GIL,我的预期结果应该是
7.3第一个响应和14.xxx第二个响应。有人能帮我验证这个问题吗?正如@ShadowRanger在评论中指出的那样,这个答案背后的核心原则(即两个进程对同一个变量倒计时,需要线程本地存储)是不正确的。但是,我将保留答案,因为它确实包含一些对OP找到解决方案有用的信息 使用GIL,Python解释器确实一次只允许运行一个线程——除了I/O操作。它有一个“切换间隔”,即它停止运行一个线程并查看运行另一个线程一段时间的频率。您可以通过以下方式进行检查:
sys.getswitchinterval()
在我的系统上设置为5ms
您看到的是两个线程,两个线程一次运行大约5ms,但都在递增相同的变量,因为您有一个与线程共享的地址空间。因此,没有两个线程分别计数到50000000,有两个线程分别计数到单个50000000的一部分
您需要的是“线程本地存储”,因此每个线程都会增加自己的计数器:
#!/usr/bin/env python3
from flask import Flask
import threading
import os, sys
app = Flask(__name__)
COUNT = 50000000
@app.route('/')
def hello():
print ('Thread id = ', threading.get_ident())
print ('Process id = ', os.getpid())
mydata = threading.local()
mydata.i = 0
while(mydata.i < COUNT):
mydata.i = mydata.i +1
return "Hello World!"
if __name__ == '__main__':
print(f'switch interval: {sys.getswitchinterval()}')
app.run()
如果我在线程本地存储中使用I
运行代码,我会得到:
response after 19.429346084594727
response after 19.553659200668335
正如@ShadowRanger在评论中指出的,这个答案背后的核心原则(即两个进程对同一个变量倒计时,并且需要线程本地存储)是不正确的。但是,我将保留答案,因为它确实包含一些对OP找到解决方案有用的信息 使用GIL,Python解释器确实一次只允许运行一个线程——除了I/O操作。它有一个“切换间隔”,即它停止运行一个线程并查看运行另一个线程一段时间的频率。您可以通过以下方式进行检查:
sys.getswitchinterval()
在我的系统上设置为5ms
您看到的是两个线程,两个线程一次运行大约5ms,但都在递增相同的变量,因为您有一个与线程共享的地址空间。因此,没有两个线程分别计数到50000000,有两个线程分别计数到单个50000000的一部分
您需要的是“线程本地存储”,因此每个线程都会增加自己的计数器:
#!/usr/bin/env python3
from flask import Flask
import threading
import os, sys
app = Flask(__name__)
COUNT = 50000000
@app.route('/')
def hello():
print ('Thread id = ', threading.get_ident())
print ('Process id = ', os.getpid())
mydata = threading.local()
mydata.i = 0
while(mydata.i < COUNT):
mydata.i = mydata.i +1
return "Hello World!"
if __name__ == '__main__':
print(f'switch interval: {sys.getswitchinterval()}')
app.run()
如果我在线程本地存储中使用I
运行代码,我会得到:
response after 19.429346084594727
response after 19.553659200668335
Python烧瓶中也有一个GIL 在我问的问题中,我没有单独向服务器发送请求。 实际上,一个单独的请求需要一半的时间才能得到响应。
为什么我们能同时得到两个答案?这是因为其中包含sys.getswitchinterval(),因此线程将每隔0.005秒切换一次,直到它们完成任务。Python Flask中也有一个GIL 在我问的问题中,我没有单独向服务器发送请求。 实际上,一个单独的请求需要一半的时间才能得到响应。
为什么我们能同时得到两个答案?这是因为其中包含sys.getswitchinterval(),因此线程将每隔0.005秒切换一次,直到它们完成任务。很可能flask(或运行flask的任何程序)正在将新进程分支到服务请求。GIL会伤害线程,但独立进程不会受到影响。GIL不会永远阻止线程。仍然有可能两个线程似乎同时运行,但实际上正在快速切换控制。@klauds.:在这种情况下不是;这里的延迟完全是CPU限制的循环(不是一个阻塞任务,其中一个可以工作,而另一个被阻塞)。每个线程完成的工作不能同时完成,它们必须在每次切换控件的基础上支付上下文切换的费用。如果工作是
时间.sleep(7)
或其他什么,它可以像您描述的那样工作,但是如果一个线程在纯CPU限制的工作上花费7秒,那么添加另一个线程再次执行相同的工作将使时间大约加倍。Uhg。。移动滚动失败。@ShadowRanger我想我只是找到了答案,我试着只发送一次请求,这需要一半的时间,所以这意味着GIL确实也在烧瓶上工作!!!!!!!很有可能flask(或者运行flask的任何东西)正在将新的流程分支到服务请求。GIL会伤害线程,但独立进程不会受到影响。GIL不会永远阻止线程。仍然有可能两个线程似乎同时运行,但实际上正在快速切换控制。@klauds.:在这种情况下不是;这里的延迟完全是CPU限制的循环(不是一个阻塞任务,其中一个可以工作,而另一个被阻塞)。每个线程完成的工作不能同时完成,它们必须在每次切换控件的基础上支付上下文切换的费用。如果工作是时间.sleep(7)
或其他什么,它可以像您描述的那样工作,但是如果一个线程在纯CPU限制的工作上花费7秒,那么添加另一个线程再次执行相同的工作将使时间大约加倍。Uhg。。移动滚动失败。@ShadowRanger我想我只是找到了答案,我试着只发送一次请求,这需要一半的时间,所以这意味着GIL确实也在烧瓶上工作!!!!!!!这并不能解释它i
是函数的局部变量,而不是全局变量,因此它不在线程之间共享。只有COUNT
是全局的/共享的,并且它永远不会变异。@Shad