二、并发编程之——线程
什么是线程?每一个进程中,都至少有一个线程。就像是我们上课听老师讲课,不仅耳朵要听,脑袋还要跟着思考。如果一个程序在运行时,只有进程,那么如果遇到阻塞,程序就运行不下去了。那么如何开启线程呢?上代码:
import time from threading import Thread def func(i): time.sleep(1) Thread(target=func,args=(1,)).start()
1.线程的特点
1)每个进程里至少有一个主线程负责执行代码。
2)在主线程中可以再开启一个新的线程
3)在同一个进程中就有两个线程同时在工作
4)线程才是CPU调度的最小单位
5)多个线程之间的数据是共享的
6)使用多线程处理高计算型场景 Python并不占有事
7)在用一个进程中,同一时刻,只能有一个线程访问CPU
注:Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
2.守护线程
什么是守护线程?同进程的道理一样,主线程结束了之后守护线程也同时结束,守护线程会等待主线程完全结束之后才结束。
3.锁
1)死锁
什么是死锁?是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。下面代码就会产生死锁现象。
from threading import Lock as Lock import time mutexA=Lock() mutexA.acquire() mutexA.acquire() print(123) mutexA.release() mutexA.release()
解决办法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,Python提供了可重入锁RLock。这个RLock内部维护者一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个县城所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,就不会出现死锁。即:
from threading import RLock as Lock import time mutexA=Lock() mutexA.acquire() mutexA.acquire() print(123) mutexA.release() mutexA.release()
4.信号量
同进程的一样,Semaphore管理一个内置的计数器,例如同时只有5个线程可以获得semaphore,即可以限制最大连接数是5.
5.事件
同进程的一样
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度;
event.clear():恢复event的状态值为False。
6.线程池
同进程池一样,假如我们忙时需要跑成百上千万个任务,闲时可能只有零零星星个任务。那么在成百上千万个任务需要被执行时,我们就需要去创建上千万个进程/线程吗?首先,无论创建进程还是线程都是有时间消费的,销毁进程/线程也是有时间消费的,并且,即使创建出了成百上千万个进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或结束进程/线程。下面是一个简单的池子:
import time from concurrent.futures import ThreadPoolExecutor def func(num): time.sleep(1) print(num) t= ThreadPoolExecutor(20) for i in range(100): t.submit(func,i) t.shutdown() # join了整个池子
这个池子中放了20个线程,随用随取,用完放回。这样就大大提高了我们的执行效率。
实例:模拟多线程网页爬取
import time import random from concurrent.futures import ThreadPoolExecutor urls = [ 'www.baidu.com', 'www.sogou.com', 'www.sohu.com', 'www.sina.com', 'www.163.com', 'www.python.org', ] def analies(content): print('分析网页') print(content.result()) def get_url(url): time.sleep(random.uniform(1,3)) return url*10 t = ThreadPoolExecutor(3) for url in urls: t.submit(get_url,url).add_done_callback(analies)