上一篇博客讲了进程、线程、协程和GIL的基本概念,这篇咱们来讲说在如下三点:html
1> python中使用threading库来建立线程的两种方式python
2> 使用Event对消来判断线程是否已启动微信
3> 使用Semaphore和BoundedSemaphore两个类分别来控制线程的并发数以及两者之间的区别。并发
若是想要了解基本概念,请移步个人上一篇博客:http://www.javashuo.com/article/p-qbymlkcy-o.htmlapp
正文:函数
利用threading库来在建立一个线程:ui
from threading import Thread def run(name): print('我是: %s' % (name)) if __name__ == '__main__': t = Thread(target=run, args=('线程1号',)) t.start()
运行结果: spa
首先,建立一个Thread线程,并用target=run来指定这个线程须要执行那个run方法,而后将run方法所需的参数以args参数的形式传递过去,操作系统
请注意,args所传的参数是一个元组(tuple)类型,所以即便元组中只有一个参数,也要在这个参数后再加一个逗号,如 args=('线程1号',)线程
并且,当咱们建立一个线程对象后,这个线程并不会当即执行,除非调用它的star()方法,当调用star()方法后,这个线程会调用你使用target传给他的函数,
并将args中的参数传递给这个函数。
Python中的线程会在一个单独的系统级线程中执行(如一个POSIX线程或一个Windows线程),这些线程所有由操做系统进行管理,线程一旦启动,
它将独立运行直至目标函数运行结束。
咱们能够调用is_alive()方法来进行判断该线程是否在运行(is_alive()方法返回True或者False):
咱们知道,进程是依赖于线程来执行的,因此当咱们的py文件在执行时,咱们能够理解为有一个主线程在执行,当咱们建立一个子线程时,就至关于当前程序一共有两个线程在执行。当子线程被建立后,主线程和子线程就独立运行,相互并不影响。
代码以下:
1 import time 2 from threading import Thread 3 4 def run(name): 5 time.sleep(2) 6 print('我是: %s' % (name)) 7 8 if __name__ == '__main__': 9 t = Thread(target=run, args=('子线程',)) 10 t.start() 11 print('我是主线程')
执行结果:
在代码的第9行,建立了一个线程t,让它来执行run()方法,这时,程序中就有了两个线程同时存在、各自独立运行,默认的,主线程是不会等待子线程的运算结果的,因此主线程继续向下执行,打印L“我是主线程”,而子线程在调用run()方法时sleep了两秒钟,以后才打印出“我是子线程”
固然,咱们能够手动的调用join()方法来让主线程等待子线程运行结束后再向下执行:
1 import time 2 from threading import Thread 3 4 def run(name): 5 time.sleep(2) 6 print('我是: %s' % (name)) 7 8 if __name__ == '__main__': 9 t = Thread(target=run, args=('子线程',)) 10 t.start() 11 t.join() 12 print('我是主线程')
这时,程序在进行到第十行后,主线程就卡住了,它在等待子线程运行结束,档子线程运行结束后,主线程才会继续向下运行,直至程序退出。
可是,可是,不管主线程等不等待子线程,Python解释器都会等待全部的线程都终止后才会退出。也就是说,不管主线程等不等待子线程,这个程序最终都
会运行两秒多,由于子线程sleep了两秒。
因此,当遇到须要长时间运行的线程或者是须要一直在后台运行的线程时,能够将其设置为后台线程(守护线程)daemon=True,如:
t = Thread(target=run, args=('子线程',), daemon=True)
守护线程,顾名思义是守护主线程的线程,他们是依赖于主线程而存在的,当主线程执行结束后,守护线程会被当即注销,不管该线程是否执行结束。
固然,咱们也能够利用join()来使主线程等待主线程。
使用threading库来建立线程还有一种方式:
from threading import Thread class CreateThread(Thread): def __init__(self): super().__init__() def run(self): print('我是子线程!') t = CreateThread() t.start()
在开始t = Thread(target=run, args=('子线程',))这种方式调用方法时子线程调用的run方法的这个方法名是我随便起的,实际上叫什么都行,
可是以继承Thread类方式实现线程时,线程调用的方法名必须是run() 这个是程序写死的。
使用Event对象判断线程是否已经启动
threading库中的Event对象包含一个可由线程来设置的信号标志,它容许线程等待某些事件的发生。
初始状态时,event对象中的信号标志被设置为假,若是有一个线程等待event对象,且这个event对象的标志为假,那么这个线程就会一直阻塞,直到该标志为真。若是将一个event对象的标志设置为真,他将唤醒全部等待这个标志的线程,若是一个线程等待一个被设置为真得Event对象,那么它将忽略这个事件,继续向下执行。
Event (事件) 定义了一个全局的标志Flag,若是Flag为False,当程序执行event.wait()时就会阻塞,当Flag为True时,程序执行event.wait()时便不会阻塞:
event.set(): 将标志Flag设置为True, 并通知全部因等待该标志而处于阻塞状态的线程恢复运行。
event.clear(): 将标志Flag设置为False
event.wait(): 判断当前标志状态,若是是True则当即返回,不然线程继续阻塞。
event.isSet(): 获取标志Flag状态: 返回True或者False
1 from threading import Thread, Event 2 3 def run(num, start_evt): 4 if int(num) >10: 5 start_evt.set() 6 else: 7 start_evt.clear() 8 start_evt = Event() 9 10 if __name__ == '__main__': 11 num = input("请输入数字>>>") 12 t = Thread(target=run, args=(num, start_evt,)) 13 t.start() 14 start_evt.wait() # 主线程获取Event对象的标志状态,若为True,则主线程继续执行,不然,主线程阻塞 15 print("主线程继续执行!")
上边这段代码:当输入的数字大于10时,将标志设置为True,主程序继续执行,当小于或者等于10时,将标志设为False(默认为False),主线程阻塞。
值得注意的是:当Event对象的标志被设置为True时,他会唤醒全部等待他的线程,若是只想唤醒某一个线程,最好使用信号量。
信号量
信号量,说白了就是一个计数器,用来控制线程的并发数,每次有线程得到信号量的时候(即acquire())计数器-1,释放信号量时候(release())计数器+1,计数器为0的时候其它线程就被阻塞没法得到信号量
acquire() # 设置一个信号量
release() # 释放一个信号量
python中有两个类实现了信号量:(Semaphore和BoundedSemaphore)
Semaphore和BoundedSemaphore的相同之处:
经过: threading.Semaphore(3) 或者 threading.BoundedSemaphore(3) 来设置初始值为3的计数器
执行acquire() 计数器-1,执行release() 计数器+1,当计数器为0时,其余线程均没法再得到信号量从而阻塞
import threading se = threading.BoundedSemaphore(3) for i in range(5): se.acquire() print('信号量被设置') for j in range(10): se.release() print('信号量被释放了')
执行结果:
import threading se = threading.Semaphore(3) for i in range(5): se.acquire() print('信号量被设置') for j in range(10): se.release() print('信号量被释放了')
执行结果:
能够看到,不管咱们用那个类建立信号量,当计数器被减为0时,其余线程均会阻塞。
这个功能常常被用来控制线程的并发数:
没有设置信号量:
import time import threading num = 3 def run(): time.sleep(2) print(time.time()) if __name__ == '__main__': t_list = [] for i in range(20): t = threading.Thread(target=run) t_list.append(t) for i in t_list: i.start()
执行结果:20个线程几乎在同时执行,,若是主机在执行IO密集型任务时执行这种程序时,主机有可能会宕机,
可是在设置了信号量时,咱们能够来控制同一时间同时运行的线程数:
import time import threading num = 3 def run(): se.acquire() # 添加信号量 time.sleep(2) print(time.time()) se.release() # 释放一个信号量 if __name__ == '__main__': t_list = [] se = threading.Semaphore(5) # 设置一个大小为5的计数器(同一时间,最多容许5个线程在运行)
# se = threading.BoundedSemaphore(5)
for i in range(20): t = threading.Thread(target=run) t_list.append(t) for i in t_list: i.start()
这时,咱们给程序加上信号量,控制它在同一时间内,最多只有5个线程在运行。
二者之间的差别性:
当计数器达到设定好的上线时,BoundedSemaphore就没法进行release()操做了,Semaphore没有这个限制,它会抛出异常。
import threading se = threading.Semaphore(3) for i in range(3): # 将计数器值减为0 se.acquire(3) for j in range(5): # 将计数器值加至5 se.release() print('信号量被释放了')
运行结果:
import threading se = threading.BoundedSemaphore(3) for i in range(3): # 将计数器值减为0 se.acquire(3) for j in range(5): # 将计数器值加至5 se.release() print('信号量被释放了')
运行结果:
抛异常了:信号量被释放太屡次。。。
好了,这篇文章的就先写到这里,下一篇文章我会讲解关于线程间通讯、线程加锁等问题
想了解更多Python关于爬虫、数据分析的内容,欢迎你们关注个人微信公众号:悟道Python