并发编程之线程2
1、GIL锁:
1、什么是GIL
全局解释器锁,本质就是一把互斥锁,是加到解释器身上的,每一个python进程内都有这么一把锁
2、有了GIL会对单进程下的多个线程造成什么样的影响
多线程要想执行,首先需要争抢GIL,对所有待执行的线程来说,GIL就相当于执行权限,
同一时刻只有一个线程争抢成功,即单进程下的多个线程同一时刻只有一个在运行
意味着单进程下的多线程没有并行的效果,但是有并发的效果
ps:分散于不同进程内的线程不会去争抢同一把GIL,只有同一个进程的多个线程才争抢同一把GIL
3、为什么要有GIL
Cpython解释器的内存管理机制不是线程安全的,所以需要有一把控制同一时间解释器的使用权
4、GIL与自定义互斥锁的异同,多个线程争抢GIL与自定义互斥锁的过程分析
相同:
都是互斥锁
不同点:
GIL是加到解释器上的,作用于全局
自定义互斥锁作用于局部
单进程内的所有线程都会去抢GIL
单进程内的只有一部分线程会去抢自定义的互斥锁
5、什么时候用python的多线程,什么时候用多进程,为什么?
单进程下的多个线程是无法并行,无法并行意味着不能利用多核优势
cpu干的计算的活,多个cpu意味提升了计算性能,
cpu是无法做IO操作,多个cpu在IO操作面前毫无用处
当我们的程序是IO密集型的情况下,多核对性能的提升微不足道,此时可以使用python的多线程
当我们的程序是计算密集型的情况下,一定要用上多核优势,此时可以使用python的多进程
2、进程池vs线程池
池:装固定数目的东西,东西指的是进程或者线程
池子内什么时候装进程:并发的任务属于计算密集型
池子内什么时候装线程:并发的任务属于IO密集型
为什么要用?
让机器在自己可承受的范围内去保证一个高效的工作
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time,os,random
def task(x):
print('%s 接客' %x)
time.sleep(random.randint(1,3))
return x**2
if __name__ == '__main__':
# 异步调用
p=ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5
# alex,武佩奇,杨里,吴晨芋,张三
obj_l=[]
for i in range(10):
obj=p.submit(task,i)
obj_l.append(obj)
# p.close()
# p.join()
p.shutdown(wait=True)
# shutdown指的是不能再往进程池内提交任务,wait=True等待进程池或线程池内所有的任务都运行完毕
print(obj_l[3].result())
print('主')
3、阻塞vs非阻塞 同步vs异步
阻塞、就绪、运行指的是进程的三种执行状态
阻塞=》阻塞:遇到io操作
非阻塞=》就绪、运行
总结:阻塞与非阻塞指的是程序的执行状态
同步与异步:指的是提交任务的两种方式
同步:提交完任务后就在原地等待,直到任务运行完毕,拿到返回值,才继续执行下一行代码
异步:提交完任务后不在原地等待,直接执行下一行代码,结果?
1、阻塞与非阻塞指的是程序的两种运行状态
阻塞:遇到IO就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放CPU资源
非阻塞(就绪态或运行态):没有遇到IO操作,或者通过某种手段让程序即便是遇到IO操作也不会停在原地,执行其他操作,力求尽可能多的占有CPU
2、同步与异步指的是提交任务的两种方式:
同步调用:提交完任务后,就在原地等待,直到任务运行完毕后,拿到任务的返回值,才继续执行下一行代码
异步调用:提交完任务后,不在原地等待,直接执行下一行代码,结果?
4、异步+回调机制
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread import requests import os import time import random def get(url): print('%s GET %s' %(current_thread().name,url)) response=requests.get(url) time.sleep(random.randint(1,3)) if response.status_code == 200: # 干解析的活 return response.text #等到下载功能完成,返回结果 def pasrse(obj): res=obj.result() print('%s 解析结果为:%s' %(current_thread().name,len(res))) if __name__ == '__main__': urls=[ 'https://www.baidu.com', 'https://www.baidu.com', 'https://www.baidu.com', 'https://www.baidu.com', 'https://www.baidu.com', 'https://www.baidu.com', 'https://www.baidu.com', 'https://www.baidu.com', 'https://www.python.org', ] pool=ThreadPoolExecutor(4) #指定同一时刻只能有4个线程在工作 for url in urls: obj=pool.submit(get,url) obj.add_done_callback(pasrse) #把下载结果给到解析功能解析,实现下载与解析的并发 print('主线程',current_thread().name) #解析功能其实是主线程做完的,因为此时只有主线程空闲
线程的其他属性方法(了解):queue,Event
import queue # q=queue.Queue(3) #队列:先进先出 # q.put(1) # q.put(2) # q.put(3) # # q.put(4) # # print(q.get()) # print(q.get()) # print(q.get()) # q=queue.LifoQueue(3) #堆栈:后进先出 # # q.put('a') # q.put('b') # q.put('c') # # print(q.get()) # print(q.get()) # print(q.get()) q=queue.PriorityQueue(3) #优先级队列:可以以小元组的形式往队列里存值,第一个元素代表优先级,数字越小优先级越高 q.put((10,'user1')) q.put((-3,'user2')) q.put((-2,'user3')) print(q.get()) print(q.get()) print(q.get())
Event:
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
from threading import Thread,Event import threading import time,random def conn_mysql(): count=1 while not event.is_set(): if count > 3: raise TimeoutError('链接超时') print('<%s>第%s次尝试链接' % (threading.current_thread().getName(), count)) event.wait(0.5) count+=1 print('<%s>链接成功' %threading.current_thread().getName()) def check_mysql(): print('\033[45m[%s]正在检查mysql\033[0m' % threading.current_thread().getName()) time.sleep(random.randint(2,4)) event.set() if __name__ == '__main__': event=Event() conn1=Thread(target=conn_mysql) conn2=Thread(target=conn_mysql) check=Thread(target=check_mysql) conn1.start() conn2.start() check.start()
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。