Python 多任务的实现有3种方式:
- 多进程模式;
- 多线程模式;
- 多进程+多线程模式。
多线程编程
线程的状态:
- New 创建
- Runnable 就绪。等待调度
- Running 运行
- Blocked 阻塞。阻塞可能在 Wait Locked Sleeping
- Dead 消亡
线程的类型:
- 主线程
- 子线程
- 守护线程(后台线程)
- 前台线程
1、线程的创建
Python中使用线程有两种方式:
1. 函数式(_thread模块)
函数式:调用 _thread 模块中的start_new_thread()函数来产生新线程。语法如下:
_thread.start_new_thread ( function, args[, kwargs] )
#参数说明:function - 线程要执行的函数。args - 传递给线程函数的参数,必须是个tuple类型。kwargs - 可选参数。
实例如下:
import _thread
import time
#为线程定义一个函数
def print_time(threadName,delay):
count = 0
while count < 5:
time.sleep(delay) #time.sleep(t) 推迟t秒调用线程的运行
count += 1
print("%s:%s",format( threadName, time.ctime(time.time()))) #time.ctime()返回当前时间的可读形式
#创建两个线程
try:
_thread.start_new_thread(print_time,("thread1",2))
_thread.start_new_thread(print_time,("thread2",4))
except:
print("Error:无法启动线程")
while 1 :
pass
执行输出结果如下:
2. 用类来包装线程对象(threading模块)
_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。
threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:
- threading.currentThread(): 返回当前的线程变量。
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
- run(): 用以表示线程活动的方法。
- start():启动线程活动。
- join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
- isAlive(): 返回线程是否活动的。
- getName(): 返回线程名。
- setName(): 设置线程名。
通过直接从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程,即它调用了线程的 run() 方法:
import threading
import time
#定义新线程子类
class myThread(threading.Thread):
def __init__(self,threadID,name,delay):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.delay = delay
def run(self):
print ("开始线程:" + self.name)
print_time(self.name, self.delay, 5)
print ("退出线程:" + self.name)
#线程函数
def print_time(threadName,delay,counter):
while counter:
time.sleep(delay)
print("{}: {}".format(threadName, time.ctime(time.time())))
counter -= 1
#创建新线程
thread1 = myThread(1,"Thread1",1)
thread2 = myThread(2,"Thread2",2)
# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主线程")
执行输出结果如下:
2 . 线程同步
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。
多线程的优势在于可以同时运行多个任务(并发)。threading
模块给我们提供了一个 Lock 功能,访问资源的线程需要获得锁才能访问。
lock = threading.Lock()
获取锁
lock.acquire()
释放锁:
lock.release()
Python 提供了可重入锁(RLock)。RLock 内部维护着一个 Lock 和一个 counter 变量,counter 记录了 acquire 的次数,从而使得资源可以被多次 require。直到一个线程所有的 acquire 都被 release,其他的线程才能获得资源。
r_lock = threading.RLock()
实例:
import threading
import time
class myThread (threading.Thread):
def __init__(self, threadID, name, delay):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.delay = delay
def run(self):
print ("开启线程: " + self.name)
# 获取锁,用于线程同步
threadLock.acquire()
print_time(self.name, self.delay, 3)
# 释放锁,开启下一个线程
threadLock.release()
def print_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print ("{}: {}".format(threadName, time.ctime(time.time())))
counter -= 1
threadLock = threading.Lock()
# 创建新线程
threads = [myThread(i,"Thread-"+i,i)for i in range(1,3)]
# 开启新线程
for thread in threads:
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join()
print ("退出主线程")
执行输出结果如下:
3. 线程优先级队列( Queue)(线程间通信方式)
从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue
对象,这些线程通过使用 put()
和 get()
操作来向队列中添加或者删除元素。
Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。
Queue 模块中的常用方法:
- Queue.qsize() 返回队列的大小
- Queue.empty() 如果队列为空,返回True,反之False
- Queue.full() 如果队列满了,返回True,反之False
- Queue.full 与 maxsize 大小对应
- Queue.get([block[, timeout]])获取队列,timeout等待时间
- Queue.get_nowait() 相当Queue.get(False)
- Queue.put(item) 写入队列,timeout等待时间
- Queue.put_nowait(item) 相当Queue.put(item, False)
- Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
- Queue.join() 实际上意味着等到队列为空,再执行别的操作
实例:
import queue,time,threading
exitFlag = 0
#定义一个线程类
class myThread (threading.Thread):
def __init__(self, threadID, name, q):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.q = q #队列
def run(self):
print ("开启线程:" + self.name)
process_data(self.name, self.q)
print ("退出线程:" + self.name)
# 获取数据函数
def process_data(threadName, q):
while not exitFlag:
queueLock.acquire()
if not workQueue.empty(): #队列非空,取出,否则释放队列锁
data = q.get()
queueLock.release()
print ("{} processing {}".format(threadName, data))
else:
queueLock.release()
time.sleep(1)
threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
# 填充队列
queueLock.acquire()
for word in nameList:
workQueue.put(word)
queueLock.release()
#实例化新线程
threads = [myThread(threadID+1, tName, workQueue) for threadID,tName in enumerate(threadList)]
# 等待所有线程完成
for t in threads:
t.start()
t.join()
# 等待队列清空
while not workQueue.empty():
pass
# 通知线程是时候退出
exitFlag = 1
for t in threads:
t.join() #join()方法,等待至线程中止
print ("退出主线程")
执行输出结果如下:
Python 还提供了 Event 对象用于线程间通信(实现同步),它是由线程设置的信号标志,如果信号标志为真,则其他线程等待直到信号解除。
Event 对象实现了简单的线程通信机制,它提供了设置信号,等待,清除信号等用于实现线程间的通信。
- 设置信号:
set()
方法设置 Event 对象内部的信号标志为真。Event 对象提供了isSe()
方法来判断其内部信号标志的状态。当使用 event 对象的set()
方法后,isSet()
方法返回真 - 清除信号:使用 Event 对象的
clear()
方法可以清除 Event 对象内部的信号标志,即将其设为假,当使用 Event 的 clear 方法后,isSet() 方法返回假 - 等待:wait 的方法只有在内部信号为真的时候才会很快的执行并完成返回。当 Event 对象的内部信号标志位假时,则 wait 方法一直等待到其为真时才返回。
实例:
#线程列表中的线程,信号为真则清除信号并等待,为假则输出线程名,并设置信号为真
import threading
#定义一个线程类
class mThread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name=threadname)
def run(self):
# global使用全局Event对象
global event
# 判断Event对象内部信号标志
if event.isSet(): #真则清除
event.clear()
event.wait()
print(self.getName())
else:
print(self.getName())
#设置Event对象内部信号标志
event.set()
# 生成Event对象
event = threading.Event()
t1 = [] # 线程列表
for i in range(10):
t = mThread(str(i))
# 生成线程列表
t1.append(t)
# 运行线程
for i in t1:
i.start()
4、Condition 条件变量
Python 提供了 Condition 对象。使用 Condition 对象可以在某些事件触发或者达到特定的条件后才处理数据,Condition 除了具有 Lock 对象的 acquire 方法和 release 方法外,还提供了 wait 和 notify 方法。线程首先 acquire 一个条件变量锁。如果条件不足,则该线程 wait,如果满足就执行线程,甚至可以 notify(通知) 其他线程。其他处于 wait 状态的线程接到通知后会重新判断条件。
其中条件变量可以看成不同的线程先后 acquire 获得锁,如果不满足条件,可以理解为被扔到一个( Lock 或 RLock )的 waiting 池。直达其他线程 notify 之后再重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。该模式常用于生产者消费者模式,具体看看下面在线购物买家和卖家的示例:
import threading, time
#买家
class Consumer(threading.Thread):
def __init__(self, cond, name):
# 初始化
super(Consumer,self).__init__()
self.cond = cond
self.name = name
def run(self):
time.sleep(1)
self.cond.acquire()
print(self.name + ': 我这两件商品一起买,可以便宜点吗')
self.cond.notify()
self.cond.wait()
print(self.name + ': 我已经提交订单了,你修改下价格')
self.cond.notify()
self.cond.wait()
print(self.name + ': 收到,我支付成功了')
self.cond.notify()
self.cond.wait()
print(self.name + ': 那我就等你发货了')
self.cond.notify()
self.cond.release()
#卖家
class Producer(threading.Thread):
def __init__(self, cond, name):
super(Producer,self).__init__()
self.cond = cond
self.name = name
def run(self):
self.cond.acquire()
# 释放对锁的占用,同时线程挂起在这里,直到被 notify 并重新占有琐。
self.cond.wait()
print(self.name + ': 可以的,你提交订单吧')
self.cond.notify()
self.cond.wait()
print(self.name + ': 好了,已经修改了')
self.cond.notify()
self.cond.wait()
print(self.name + ': 嗯,收款成功,马上给你发货')
self.cond.notify()
self.cond.wait()
print(self.name + ': 商品发货了')
self.cond.release()
print("主线程开始")
cond = threading.Condition()
consumer = Consumer(cond, '买家rrh')
producer = Producer(cond, '卖家tk')
consumer.start()
producer.start()
consumer.join()
producer.join()
print("主线程结束")
执行输出结果如下:
5. 后台进程(守护线程)
默认情况下,主线程退出之后,如果子线程没有 join,那么主线程结束后,子线程也依然会继续执行。如果希望主线程退出后,其子线程也退出而不再执行,则需要设置子线程为后台线程。Python 提供了 setDeamon
方法。
python中多线程编程时,经常会用到join()和setDaemon()方法,下面分别简单介绍下两种方式的概念及用法。
1.join()方法
主线程A中,创建了子线程B,并且在主线程A中调用了B.join()方法,那么主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行。也就是说那么在调用这个线程时可以使用被调用线程的join方法。
用法:
join[timeout]
里面的参数是可选的,代表线程运行的最大时间,即如果超过了这个时间,不管这个子线程有没有执行完毕都会被回收,然后主线程或函数都会被接着执行的。
2. setDaemon()方法
在启动线程前(就是必须在start()方法之前设置,)设置thread.setDaemon(True),就是设置该线程为守护线程,表示该线程是不重要的,进程退出时不需要等待这个线程执行完成。
这样做的意义在于:避免子线程无限死循环,导致退不出程序。
程序运行中,执行一个主进程,如果主线程又创建了一个子线程,主线程和子线程就兵分两路,分别运行,那么当主线程完成想要退出时,会检验子线程是否完整。如果子线程未完成,则会等待子线程完成后再退出。但有时候我们需要的是,只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法了。
多进程
Python 提供了非常好用的多进程包 multiprocessing,只需要定义一个函数,Python 会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。multiprocessing 支持子进程、通信和共享数据、执行不同形式的同步,提供了 Process、Queue、Pipe、Lock 等组件。
1. 类 Process
创建进程的类:
Process([group [, target [, name [, args [, kwargs]]]]])
- target 表示调用对象
- args 表示调用对象的参数元组
- kwargs表示调用对象的字典
- name为别名
- group实质上不使用
实例:
import time,multiprocessing
def worker(interval,name):
print(name + '【start】')
time.sleep(interval)
print(name + '【end】')
if __name__ ="__main__":
p1,p2,p3 = multiprocessing.Process(target=worker, args=(2, '两点水1')), multiprocessing.Process(target=worker, args=(2, '两点水2')), multiprocessing.Process(target=worker, args=(2, '两点水3'))
p1.start()
p2.start()
p3.start()
print("The number of CPU is:" + str(multiprocessing.cpu_count()))
for p in multiprocessing.active_children():
print("child p.name:" + p.name + "\tp.id" + str(p.pid))
print("END!!!!!!!!!!!!!!!!!")
执行输出结果如下:
也可以自己把进程创建成一个类,当进程 p 调用 start() 时,自动调用 run() (重写),实例:
import multiprocessing
import time
class ClockProcess(multiprocessing.Process):
def __init__(self, interval):
multiprocessing.Process.__init__(self)
self.interval = interval
def run(self):
n = 5
while n > 0:
print("当前时间: {0}".format(time.ctime()))
time.sleep(self.interval)
n -= 1
if __name__ == '__main__':
p = ClockProcess(3)
p.start()
p.join()
print("End")
执行输出结果如下:
如果在子进程中添加了 daemon 属性,那么当主进程结束的时候,子进程也会跟着结束,本例就会直接输出,就直接输出End了。
如果想让子进程执行完该怎么做呢?join 方法的主要作用是:阻塞当前进程,直到调用 join 方法的那个进程执行完,再继续执行当前进程。
2. Pool
如果需要很多的子进程,使用进程池的方法批量创建子进程。实例:
from multiprocessing import Pool
import os, time, random
#进程运行信息
def long_time_task(name):
print('进程的名称:{0} ;进程的PID: {1} '.format(name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('进程 {0} 运行了 {1} 秒'.format(name, (end - start)))
if __name__ == '__main__':
print('主进程的 PID:{0}'.format(os.getpid()))
p = Pool(4) #同时运行4个进程
for i in range(6):
p.apply_async(long_time_task, args=(i,)) #pply_async 是异步非阻塞的。即不用等待当前进程执行完毕,随时根据系统调度来进行进程切换。
p.close()
# 等待所有子进程结束后在关闭主进程
p.join()
print('【End】')
注意: Pool
对象调用 join()
方法会等待所有子进程执行完毕,调用 join()
之前必须先调用 close()
,调用close()
之后就不能继续添加新的 Process 了。
执行输出结果如下:
3. 进程间通信
Process 之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python 的 multiprocessing 模块包装了底层的机制,提供了Queue、Pipes 等多种方式来交换数据。以 Queue 为例,在父进程中创建两个子进程,一个往 Queue 里写数据,一个从 Queue 里读数据,如下:
from multiprocessing import Process, Queue
import os, time, random
def write(q):
# 写数据进程
print('写进程的PID:{0}'.format(os.getpid()))
for value in ['两点水', '三点水', '四点水']:
print('写进 Queue 的值为:{0}'.format(value))
q.put(value)
time.sleep(random.random())
def read(q):
# 读取数据进程
print('读进程的PID:{0}'.format(os.getpid()))
while not q.:
value = q.get(True)
print('从 Queue 读取的值为:{0}'.format(value))
if __name__ == '__main__':
# 父进程创建 Queue,并传给各个子进程
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程 pw
pw.start()
# 启动子进程pr
pr.start()
# 等待pw结束:
pw.join()
# pr 进程里是死循环,无法等待其结束,只能强行终止
pr.terminate()
执行输出结果如下: