day34 GIL锁,线程队列,线程池

  一.GIL锁(Global Interpreter Lock)

  首先,一些语言(java、c++、c)是支持同一个进程中的多个线程是可以应用多核CPU的,也就是我们会听到的现在4核8核这种多核CPU技术的牛逼之处。那么我们之前说过应用多进程的时候如果有共享数据是不是会出现数据不安全的问题啊,就是多个进程同时一个文件中去抢这个数据,大家都把这个数据改了,但是还没来得及去更新到原来的文件中,就被其他进程也计算了,导致数据不安全的问题啊,所以我们是不是通过加锁可以解决啊,多线程大家想一下是不是一样的,并发执行就是有这个问题。但是python最早期的时候对于多线程也加锁,但是python比较极端的(在当时电脑cpu确实只有1核)加了一个GIL全局解释锁,是解释器级别的,锁的是整个线程,而不是线程里面的某些数据操作,每次只能有一个线程使用cpu,也就说多线程用不了多核,但是他不是python语言的问题,是CPython解释器的特性,如果用Jpython解释器是没有这个问题的,Cpython是默认的,因为速度快,Jpython是java开发的,在Cpython里面就是没办法用多核,这是python的弊病,历史问题,虽然众多python团队的大神在致力于改变这个情况,但是暂没有解决。(这和解释型语言(python,php)和编译型语言有关系吗???待定!,编译型语言一般在编译的过程中就帮你分配好了,解释型要边解释边执行,所以为了防止出现数据不安全的情况加上了这个锁,这是所有解释型语言的弊端??)

  

  但是有了这个锁我们就不能并发了吗?当我们的程序是偏计算的,也就是cpu占用率很高的程序(cpu一直在计算),就不行了,但是如果你的程序是I/O型的(一般你的程序都是这个)(input、访问网址网络延迟、打开/关闭文件读写),在什么情况下用的到高并发呢(金融计算会用到,人工智能(阿尔法狗),但是一般的业务场景用不到,爬网页,多用户网站、聊天软件、处理文件),I/O型的操作很少占用CPU,那么多线程还是可以并发的,因为cpu只是快速的调度线程,而线程里面并没有什么计算,就像一堆的网络请求,我cpu非常快速的一个一个的将你的多线程调度出去,你的线程就去执行I/O操作了,

  详细介绍:链接:https://www.cnblogs.com/clschao/articles/9705317.html

  二. 线程队列

  class queue.Queue(maxsize=0) #先进先出

 1 import queue #不需要通过threading模块里面导入,直接import queue就可以了,这是python自带的
 2 #用法基本和我们进程multiprocess中的queue是一样的
 3 q=queue.Queue()
 4 q.put('first')
 5 q.put('second')
 6 q.put('third')
 7 # q.put_nowait() #没有数据就报错,可以通过try来搞
 8 print(q.get())
 9 print(q.get())
10 print(q.get())
11 # q.get_nowait() #没有数据就报错,可以通过try来搞
12 '''
13 结果(先进先出):
14 first
15 second
16 third
17 '''
View Code

  class queue.LifoQueue(maxsize=0) #last in fisrt out

import queue

q=queue.LifoQueue() #队列,类似于栈,栈我们提过吗,是不是先进后出的顺序啊
q.put('first')
q.put('second')
q.put('third')
# q.put_nowait()

print(q.get())
print(q.get())
print(q.get())
# q.get_nowait()
'''
结果(后进先出):
third
second
first
'''
View Code

  class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列

 1 import queue
 2 
 3 q=queue.PriorityQueue()
 4 #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
 5 q.put((-10,'a'))
 6 q.put((-5,'a'))  #负数也可以
 7 # q.put((20,'ws'))  #如果两个值的优先级一样,那么按照后面的值的acsii码顺序来排序,如果字符串第一个数元素相同,比较第二个元素的acsii码顺序
 8 # q.put((20,'wd'))
 9 # q.put((20,{'a':11})) #TypeError: unorderable types: dict() < dict() 不能是字典
10 # q.put((20,('w',1)))  #优先级相同的两个数据,他们后面的值必须是相同的数据类型才能比较,可以是元祖,也是通过元素的ascii码顺序来排序
11 
12 q.put((20,'b'))
13 q.put((20,'a'))
14 q.put((0,'b'))
15 q.put((30,'c'))
16 
17 print(q.get())
18 print(q.get())
19 print(q.get())
20 print(q.get())
21 print(q.get())
22 print(q.get())
23 '''
24 结果(数字越小优先级越高,优先级高的优先出队):
25 '''
View Code

  这三种队列都是线程安全的,不会出现多个线程抢占同一个资源或数据的情况。

  三. 线程池--python标准模块 concurrent.futures

  到这里就差我们的线程池没有讲了,我们用一个新的模块给大家讲,早期的时候我们没有线程池,现在python提供了一个新的标准或者说内置的模块,这个模块里面提供了新的线程池和进程池,之前我们说的进程池是在multiprocessing里面的,现在这个在这个新的模块里面,他俩用法上是一样的。

  为什么要将进程池和线程池放到一起呢,是为了统一使用方式,使用threadPollExecutor和ProcessPollExecutor的方式一样,而且只要通过这个concurrent.futures导入就可以直接用他们两个了

 1 concurrent.futures模块提供了高度封装的异步调用接口
 2 ThreadPoolExecutor:线程池,提供异步调用
 3 ProcessPoolExecutor: 进程池,提供异步调用
 4 Both implement the same interface, which is defined by the abstract Executor class.
 5 
 6 #2 基本方法
 7 #submit(fn, *args, **kwargs)
 8 异步提交任务
 9 
10 #map(func, *iterables, timeout=None, chunksize=1) 
11 取代for循环submit的操作
12 
13 #shutdown(wait=True) 
14 相当于进程池的pool.close()+pool.join()操作
15 wait=True,等待池内所有任务执行完毕回收完资源后才继续
16 wait=False,立即返回,并不会等待池内的任务执行完毕
17 但不管wait参数为何值,整个程序都会等到所有任务执行完毕
18 submit和map必须在shutdown之前
19 
20 #result(timeout=None)
21 取得结果
22 
23 #add_done_callback(fn)
24 回调函
主要方法

  

 1 import time
 2 import os
 3 import threading
 4 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
 5 
 6 def func(n):
 7     time.sleep(2)
 8     print('%s打印的:'%(threading.get_ident()),n)
 9     return n*n
10 tpool = ThreadPoolExecutor(max_workers=5) #默认一般起线程的数据不超过CPU个数*5
11 # tpool = ProcessPoolExecutor(max_workers=5) #进程池的使用只需要将上面的ThreadPoolExecutor改为ProcessPoolExecutor就行了,其他都不用改
12 #异步执行
13 t_lst = []
14 for i in range(5):
15     t = tpool.submit(func,i) #提交执行函数,返回一个结果对象,i作为任务函数的参数 def submit(self, fn, *args, **kwargs):  可以传任意形式的参数
16     t_lst.append(t)  #
17     # print(t.result())
18     #这个返回的结果对象t,不能直接去拿结果,不然又变成串行了,可以理解为拿到一个号码,等所有线程的结果都出来之后,我们再去通过结果对象t获取结果
19 tpool.shutdown() #起到原来的close阻止新任务进来 + join的作用,等待所有的线程执行完毕
20 print('主线程')
21 for ti in t_lst:
22     print('>>>>',ti.result())
23 
24 # 我们还可以不用shutdown(),用下面这种方式
25 # while 1:
26 #     for n,ti in enumerate(t_lst):
27 #         print('>>>>', ti.result(),n)
28 #     time.sleep(2) #每个两秒去去一次结果,哪个有结果了,就可以取出哪一个,想表达的意思就是说不用等到所有的结果都出来再去取,可以轮询着去取结果,因为你的任务需要执行的时间很长,那么你需要等很久才能拿到结果,通过这样的方式可以将快速出来的结果先拿出来。如果有的结果对象里面还没有执行结果,那么你什么也取不到,这一点要注意,不是空的,是什么也取不到,那怎么判断我已经取出了哪一个的结果,可以通过枚举enumerate来搞,记录你是哪一个位置的结果对象的结果已经被取过了,取过的就不再取了
29 
30 #结果分析: 打印的结果是没有顺序的,因为到了func函数中的sleep的时候线程会切换,谁先打印就没准儿了,但是最后的我们通过结果对象取结果的时候拿到的是有序的,因为我们主线程进行for循环的时候,我们是按顺序将结果对象添加到列表中的。
31 # 37220打印的: 0
32 # 32292打印的: 4
33 # 33444打印的: 1
34 # 30068打印的: 2
35 # 29884打印的: 3
36 # 主线程
37 # >>>> 0
38 # >>>> 1
39 # >>>> 4
40 # >>>> 9
41 # >>>> 16
ThreadPoolExecutor

ProcessPoolExecutor的使用

只需要将这一行代码改为下面这一行就可以了,其他的代码都不用变
tpool = ThreadPoolExecutor(max_workers=5) #默认一般起线程的数据不超过CPU个数*5
# tpool = ProcessPoolExecutor(max_workers=5)
用法和线程是一样的
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import threading
import os,time,random
def task(n):
    print('%s is runing' %threading.get_ident())
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':

    executor=ThreadPoolExecutor(max_workers=3)

    # for i in range(11):
    #     future=executor.submit(task,i)

    s = executor.map(task,range(1,5)) #map取代了for+submit
    print([i for i in s])
map方法
import time
import os
import threading
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def func(n):
    time.sleep(2)
    return n*n

def call_back(m):
    print('结果为:%s'%(m.result()))

tpool = ThreadPoolExecutor(max_workers=5)
t_lst = []
for i in range(5):
    t = tpool.submit(func,i).add_done_callback(call_back)
回调函数简单应用
原文地址:https://www.cnblogs.com/Knight-huang/p/10058714.html