20190103(GIL,池,阻塞,同步异步)

GIL锁

什么是GIL

GIL全局解释器锁,是防止多个线程在同一时间同时执行的、CPython解释器特有的一种互斥锁。 每一个py文件都会有自己的解释器,也就是说不同py文件的GIL都是独立的, ps:分散于不同进程内的线程不会去争抢同一把GIL,只有同一个进程的多个线程才争抢同一把GIL。

为什么需要GIL

CPython解释器的内存管理机制是非线程安全的,所以通过GIL使程序串行,可以保证数据安全,但会降低执行效率。 非线程安全属于历史遗留问题,而一旦修改,原来基于GIL的程序都要修改。

GIL带来的问题

即便多核处理器,也无法真正并行(可以并发),进程内线程只能并发,同一时刻只有一个线程可以运行

GIL的加锁和解锁时机(待补充)

加锁的时机:在调用解释器时立即加锁

解锁时机:

  • 当前线程遇到了IO时释放

  • 当前线程执行时间超过设定值时释放,解释器会检测线程的执行时间,一旦到达某个阈值,通知线程保存状态切换线程,以此来保证数据安全

多进程与多线程的使用情况分析:

1、单核时,无论是IO密集还是计算密集,GIL都没有影响; 2、多核下,IO密集型会受到GIL的影响,但是影响不大,此时优先考虑多线程处理数据; 3、多核下,计算密集型受到GIL影响较大,此时使用多进程可以提高效率,因为CPython多线程无法并行,但多进程可以。

程序运行时间对比:

# 纯计算,大量,进程更快(如果只是少量计算,线程未必慢,因为开启进程比开启线程更费时间)
from threading import  Thread
from multiprocessing import  Process
import time


a = 1
def task():
   global a
   for i in range(10000000):
       a +=1
       a * 10 / 2 - 3


# 多线程
s = time.time()
t1 = Thread(target=task)
t2 = Thread(target=task)
t3 = Thread(target=task)
t1.start()
t2.start()
t3.start()

t1.join()
t2.join()
t3.join()
print(time.time()-s)

# 多进程
if __name__ == '__main__':
   s = time.time()
   t1 = Process(target=task)
   t2 = Process(target=task)
   t3 = Process(target=task)
   t1.start()
   t2.start()
   t3.start()

   t1.join()
   t2.join()
   t3.join()

   print(time.time()-s)

 

GIL与自定义互斥锁的异同

GIL作用于全局,自定义互斥锁作用于局部 线程必须先获取GIL才能去拿自定义互斥锁 单进程内的所有线程都会去抢GIL 单进程内的只有一部分线程会去抢自定义的互斥锁

进程池与线程池

是一种容器,本质是储存进程或线程的列表 IO密集使用线程池,计算密集使用进程池 线程/进程池不仅帮我们控制线程/进程的数量,还帮我们完成了线程/进程的创建,销毁,以及任务的分配

池与信号量的区别:

信号量的作用是同一时间某一个数据能被指定数量的进程或线程访问 而池控制的是数量,没有对数据访问进行限制

进程池:

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import time,os

# 创建进程池,指定最大进程数为3,此时不会创建进程,不指定数量时,默认为CPU核数
pool = ProcessPoolExecutor(3)

def task():
   time.sleep(1)
   print(os.getpid(),"working..")

if __name__ == '__main__':
   for i in range(10):
       pool.submit(task) # 提交任务时立即创建进程

   # 任务执行完成后也不会立即销毁进程
   time.sleep(2)

   for i in range(10):
       pool.submit(task) #再有新任务时 直接使用之前已经创建好的进程来执行

输出结果:(每次可能都不同)
13760 working..
26536 working..
21388 working..
13760 working..
26536 working..
21388 working..
26536 working..
13760 working..
21388 working..
26536 working..
13760 working..
21388 working..
13760 working..
26536 working..
21388 working..
13760 working..
26536 working..
21388 working..
13760 working..
26536 working..

线程池:

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from threading import current_thread,active_count
import time,os

# 创建进程池,指定最大线程数为3,此时不会创建线程,不指定数量时,默认为CPU和核数*5
pool = ThreadPoolExecutor(3)
print(active_count()) # 只有一个主线

def task():
   time.sleep(1)
   print(current_thread().name,"working..")


for i in range(10):
   pool.submit(task) # 第一次提交任务时立即创建线程
# 任务执行完成后也不会立即销毁
time.sleep(2)

for i in range(10):
   pool.submit(task) #再有新任务是 直接使用之前已经创建好的线程来执行

阻塞vs非阻塞

阻塞、就绪、运行指的是进程的三种执行状态。 阻塞=》遇到io操作,无法继续执行,并且立刻释放CPU资源 非阻塞=》正常执行中(就绪、运行),没有遇到IO操作,或者通过某种手段让程序即便遇到IO也不会停在原地,而是执行其他操作。

总结:阻塞与非阻塞指的是程序的执行状态

同步与异步

同步与异步:指的是提交任务的两种方式

同步(调用/执行/任务/提交):提交完任务后就在原地等待,拿到返回值后才能继续执行下一行代码(可能是IO,也可能是因为计算时间较长等原因)。 异步(调用/执行/任务/提交):提交完任务后不在原地等待,不关心任务的结果,直接执行下一行代码。

异步效率高于同步。

异步提交任务时,任务可能包含IO操作,异步也可能阻塞。 同步提交任务时,也可能会因为计算复杂而卡,不等同与阻塞。

 

程序中的异步调用并获取结果:

from concurrent.futures import ThreadPoolExecutor  # 导入线程池
from threading import current_thread  # 从线程中导入查看当前线程的方法
import time

pool = ThreadPoolExecutor(3)


def task(i):
   time.sleep(0.01)
   print(current_thread().name,"working..")
   return i ** i
objs = []
for i in range(1,5):
   res_obj = pool.submit(task,i) # 异步方式提交任务,会返回一个对象用于表示任务结果,即pool.submit()会生成对象
   objs.append(res_obj)

pool.shutdown(wait=True)  # shutdown是一个阻塞函数,指的是不能再往进程池内提交任务,wait=True等待进程池或线程池内所有的任务都运行完毕,才允许代码往下继续执行。

# 从结果对象中取出执行结果
for res_obj in objs:  # 从列表中拿出对象
   print(res_obj.result()) # 由submit得到的对象会有result()方法,即对象内封装的返回值
print("over")

 

程序中的同步调用并获取结果:(等到返回结果才能继续执行)

from  concurrent.futures import ThreadPoolExecutor
import time,random

def task(i):
   print("%s run"%i)
   time.sleep(random.randint(1,3))
   return i**2  # 返回值,这也是result方法的要求。
pool = ThreadPoolExecutor(3)
for i in range(6):
   p = pool.submit(task,i)
   res = p.result()
   print(res)
print("over")
输出结果:
0 run
0
1 run
1
2 run
4
3 run
9
4 run
16
5 run
25
over

 

 

 

 

 

原文地址:https://www.cnblogs.com/realadmin/p/10215068.html