今天来学习线程同步与并发,咱们先来看一下线程之间的几种通讯方式:html
Event
:事件;编程
Critical Section
:临界区;安全
Semaphone
:信号量;网络
咱们先来看看下面的实例:多线程
from threading import Thread, Event import time def teacher(event: Event): print('I am teacher , waiting for your homework') event.wait() print("I am teacher, already obtaining student's homework ") def student(event: Event): finished_homework = [] while len(finished_homework) < 10: time.sleep(1) print('I am student, finished one homework') finished_homework.append(1) else: print('student finish homework') event.set() if __name__ == '__main__': event = Event() Thread(target=student, args=(event,)).start() Thread(target=teacher, args=(event,)).start()
这个实例首先从threading 模块中导入了Thread和Event两个类,而后定义了两个方法teacher和student,在if __name__ == '__main__'
中执行了这两个方法。并发
按照正常通常的代码执行顺序,会先打印teacher方法中的第一个print,而后来到了event.wait()
,wait()
方法的语法为wait(self, timeout=None)
,timeout用于设置等待的时长,若是超过期长则再也不等待,直接向下执行,若是timeout没有指定则一 直等待,等待的时候是阻塞的;app
咱们能够看到teacher方法中的event.wait()
并无设置timeout,因此会阻塞,开始执行student方法,student方法里面是一个while循环,须要打印10遍print中的内容,打印完毕后进入else,其中有一个event.set()
方法,会将flag设置为True,wait等待的线程就能够向下执行;函数
因此刚刚teacher方法中等待的会继续执行,咱们来看一下输出结果:学习
I am teacher , waiting for your homework I am student, finished one homework I am student, finished one homework I am student, finished one homework I am student, finished one homework I am student, finished one homework I am student, finished one homework I am student, finished one homework I am student, finished one homework I am student, finished one homework I am student, finished one homework student finish homework I am teacher, already obtaining student's homework
event实例对象的对象方法介绍:fetch
wait(self, timeout=None):timeout
为设置等待的时长,若是超过期长(返回值为False)则再也不等待,直接向下执行,若是timeout没有指定则一 直等待,等待的时候是阻塞的没有返回值,;
set()
:若是执行event.set(),将会设置flag为True,那么wait等待的线程就能够向下执行;
clear()
:若是执行event.clear(),将会设置flag标记为Flase, 那么wait等待的线程将再次等待(阻塞);
is_set()
:判断event的flag是否为True,若是为True的话wait等待的线程将向下执行;
thread.allocate_lock() threading.Lock()
锁是解决临界区资源的问题,保证每个线程访问临界资源的时候有所有的权利,一旦某个线程得到锁, 其它试图获取锁的线程将被阻塞;
locked()方法
:用于判断当前是否上锁,若是上锁,返回True,不然返回False;
acquire(blocking=True,timeout=-1)
:表示加锁,默认True为加锁状态(阻塞),False为不阻塞,timeout为设置时长;
release()方法
:用于释放锁,在完成任务的时候释放锁,让其余的线程获取到临界资源,注意此时必须处于上锁的状态,若是试图释放一个unlocked 的锁,将抛出异常 thread.error
;
咱们经过两个实例来看一下线程锁的做用:
实例1: import time from threading import Thread homework_list = [] def student(number): while len(homework_list) < number: # 知足条件则继续执行下面的代码 time.sleep(0.001) # 等待0.001秒 homework_list.append(1) print(len(homework_list)) if __name__ == '__main__': for i in range(10): Thread(target=student, args=(10, )).start() # 同时开启10个线程 time.sleep(3) # 等待3秒 print('完成做业的总数为: {}'.format(len(homework_list)))
运行实例1,咱们发现最终输出结果完成做业的总数为:19,可是根据代码逻辑来看,应该输出10才对,这是为何呢?实际上是由于在多线程状况下操做临界资源,出现了临界资源争抢的问题,那如何解决这个问题呢,咱们来看实例2;
实例2: import time import threading from threading import Thread, Lock homework_list = [] lock = Lock() # 全局阻塞锁 def student(number): while True: lock.acquire() # 必定要在获取临界资源以前加锁 if len(homework_list) >= number: # 知足条件则跳出循环,不知足则继续 break time.sleep(0.001) # 等待0.001秒 homework_list.append(1) lock.release() # 完成任务的时候释放锁,让其余的线程获取到临界资源 print('current_thread={}, homework_list={}'.format(threading.current_thread().name, len(homework_list))) if __name__ == '__main__': for i in range(10): Thread(target=student, name='student {}'.format(i), args=(10, )).start() # 同时开启10个线程 time.sleep(3) # 等待3秒 print('完成做业的总数为: {}'.format(len(homework_list)))
运行实例2发现最终输出完成做业的总数为: 10,是咱们想要的结果,实例1和实例2 不一样之处在于实例2在获取临界资源以前加了锁,也就是lock.acquire()
,咱们知道一旦某个线程得到锁, 其它试图获取锁的线程将被阻塞,因此这里不会再发生临界资源争抢的问题了;
with lock
,会默认自动释放锁,不须要再使用release()方法来释放锁了,这样能够避免程序中忘记释放锁,更加方便;import time import threading from threading import Thread, Lock homework_list = [] # 全局阻塞锁 lock = Lock() def student(number): while True: with lock: if len(homework_list) >= number: break time.sleep(0.001) homework_list.append(1) print('current_thread={}, homework_list={}'.format(threading.current_thread().name, len(homework_list))) if __name__ == '__main__': for i in range(10): Thread(target=student, name='student {}'.format(i), args=(1000, )).start() time.sleep(3) print('完成做业的总数为: {}'.format(len(homework_list)))
因此其实这样写,和上面的实例2是同样的效果哟,而且省略了释放锁的步骤,由于with默认自动释放锁,这样就不怕忘记释放锁而致使代码报错了;
接下来学习一下线程池,也就是ThreadPoolExecutor
,线程池在构造实例的时候,会传入max_workers参数来设置线程池中最多能同时运行的线程数目;
线程池实例对象有两个很是使用的方法,submit方法和map方法:
submit(self, fn, *args, **kwargs)
:提交线程须要执行的任务(函数名和参数)到线程池中,并返回该任务的句柄,用于提交单个任务;
map(self, fn, *iterables, timeout=None, chunksize=1)
:相似高阶函数map,能够提交任务,且传递一个可迭代对象,返回任务处理迭代对象的结果;
from concurrent.futures import ThreadPoolExecutor import requests def fetch_url(url): result = requests.get(url=url, ) return result.text # 建立10个线程队列的线程池 pool = ThreadPoolExecutor(10) # 获取任务返回对象 a = pool.submit(fetch_url, 'http://www.baidu.com') # 取出返回的结果 x = a.result() print(x) # 得到百度的源码
尽管Python彻底支持多线程编程, 可是解释器的C语言实现部分在彻底并行执行时并非线程安全的,实际上,解释器被一个全局解释器锁保护着 ,它确保任什么时候候都只有一个Python线程执行;
GIL最大的问题就是Python的多线程程序并不能利用多核CPU的优点, 就是由于GIL的存在,使得一个进程的多个线程在执行任务的时候,一个CPU时 间片内,只有一个线程可以调度到CPU上运行;
所以CPU密集型的程序通常不会使用Python实现,能够选择Java,GO等语言;
可是对于非CPU密集型程序,例如IO密集型程序,多数时间都是对网络IO的等待,所以Python的多线程彻底能够胜任;
对于全局解释器锁的解决方案:
使用multiprocessing建立进程池对象,实现多进程并发,这样就可以使用多CPU计算资源;
使用C语言扩展,将计算密集型任务转移给C语言实现去处理,在C代码实现部分能够释放GIL;
多线程和多进程解决方案: