一、线程
多任务可以由多进程完成,也可以由一个进程内的多线程完成,一个进程内的所有线程,共享同一块内存python中创建线程比较简单,导入threading模块,下面来看一下代码中如何创建多线程。
def f1(i):
time.sleep(1)
print(i)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=f1, args=(i,))
t.start()
print('start') # 主线程等待子线程完成,子线程并发执行
>>start
>>2
>>1
>>3
>>0
>>4
主线程从上到下执行,创建5个子线程,打印出'start',然后等待子线程执行完结束,如果想让线程要一个个依次执行完,而不是并发操作,那么就要使用join方法。下面来看一下代码
import threading
import time
def f1(i):
time.sleep(1)
print(i)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=f1, args=(i,))
t.start()
t.join()
print('start') # 线程从上到下依次执行,最后打印出start
>>0
>>1
>>2
>>3
>>4
>>start
上面的代码不适用join的话,主线程会默认等待子线程结束,才会结束,如果不想让主线程等待子线程的话,可以子线程启动之前设置将其设置为后台线程,如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止,前台线程则相反,若果不加指定的话,默认为前台线程,下面从代码来看一下,如何设置为后台线程。例如下面的例子,主线程直接打印start,执行完后就结束,而不会去等待子线程,子线程中的数据也就不会打印出来
import threading
import time
def f1(i):
time.sleep(1)
print(i)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=f1, args=(i,))
t.setDaemon(True)
t.start()
print('start') # 主线程不等待子线程
>> start
除此之外,自己还可以为线程自定义名字,通过 t = threading.Thread(target=f1, args=(i,), name='mythread{}'.format(i)) 中的name参数,除此之外,Thread还有一下一些方法
- t.getName() : 获取线程的名称
- t.setName() : 设置线程的名称
- t.name : 获取或设置线程的名称
- t.is_alive() : 判断线程是否为激活状态
- t.isAlive() :判断线程是否为激活状态
- t.isDaemon() : 判断是否为守护线程
二、线程锁
由于线程是共享同一份内存的,所以如果操作同一份数据,很容易造成冲突,这时候就可以为线程加上一个锁了,这里我们使用Rlock,而不使用Lock,因为Lock如果多次获取锁的时候会出错,而RLock允许在同一线程中被多次acquire,但是需要用n次的release才能真正释放所占用的琐,一个线程获取了锁在释放之前,其他线程只有等待。
import threading
G = 1
lock = threading.RLock()
def fun():
lock.acquire() # 获取锁
global G
G += 2
print(G, threading.current_thread().name)
lock.release() # 释放锁
return
for i in range(10):
t = threading.Thread(target=fun, name='t-{}'.format(i))
t.start()
3 t-0
5 t-1
7 t-2
9 t-3
11 t-4
13 t-5
15 t-6
17 t-7
19 t-8
21 t-9
三、线程间通信Event
Event是线程间通信最间的机制之一,主要用于主线程控制其他线程的执行,主要用过wait,clear,set,这三个方法来实现的的,下面来看一个简单的例子,
import threading
import time
def f1(event):
print('start:')
event.wait() # 阻塞在,等待 set
print('end:')
if __name__ == '__main__':
event_obj = threading.Event()
for i in range(5):
t = threading.Thread(target=f1, args=(event_obj,))
t.start()
event_obj.clear() # 清除标志位
inp = input('>>>>:')
if inp == 'true':
event_obj.set() # 设置标志位
四、队列
可以简单的理解为一种先进先出的数据结构,比如用于生产者消费者模型,或者用于写线程池,以及前面写select的时候,读写分离时候可用队列存储数据等等,以后用到队列的地方很多,因此对于队列的用法要熟练掌握。下面首先来看一下队列提供了哪些用法
q = queue.Queue(maxsize=0) # 构造一个先进显出队列,maxsize指定队列长度,为0时,表示队列长度无限制。
q.join() # 等到队列为kong的时候,在执行别的操作
q.qsize() # 返回队列的大小 (不可靠)
q.empty() # 当队列为空的时候,返回True 否则返回False (不可靠)
q.full() # 当队列满的时候,返回True,否则返回False (不可靠)
q.put(item, block=True, timeout=None) # 将item放入Queue尾部,item必须存在,参数block默认为True,表示当队列满时,会等待
# 为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示会阻塞设置的时间,
# 如果在阻塞时间里 队列还是无法放入,则引发 queue.Full 异常
q.get(block=True, timeout=None) # 移除并返回队列头部的一个值,可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞
# 阻塞的话若此时队列为空,则引发queue.Empty异常。 可选参数timeout,表示会阻塞设置