Python多线程
threading模块
普通的单线程程序,所有的事情只能在一个线程中顺序执行,看下面的例子,每个函数都需要2s,所以两个函数执行完需要4s钟
from threading import Thread
from time import sleep,ctime
def func1(nsec):
print('start func1 at:', ctime())
sleep(nsec)
print('func1 done at:', ctime())
def func2(nsec):
print('start func2 at:', ctime())
sleep(nsec)
print('func2 done at:', ctime())
def main():
func1(2)
func2(2)
print('all Done at:', ctime())
if __name__ == '__main__':
main()
执行结果为
start func1 at: Fri Jan 13 11:08:32 2017
func1 done at: Fri Jan 13 11:08:34 2017
start func2 at: Fri Jan 13 11:08:34 2017
func2 done at: Fri Jan 13 11:08:36 2017
all Done at: Fri Jan 13 11:08:36 2017
现在加入多线程处理,线程1执行func1,线程2执行func2
from threading import Thread
from time import sleep,ctime
def func1(nsec):
print('start func1 at:', ctime())
sleep(nsec)
print('func1 done at:', ctime())
def func2(nsec):
print('start func2 at:', ctime())
sleep(nsec)
print('func2 done at:', ctime())
def main():
threads = []
t1 = Thread(target=func1, args=(2, ))
threads.append(t1)
t2 = Thread(target=func2, args=(2, ))
threads.append(t2)
for t in threads:
t.start()
print('all Done at:', ctime())
if __name__ == '__main__':
main()
通过threading模块的Thread创建线程,利用start函数启动线程。
执行结果如下:
start func1 at:all Done at:start func2 at: Fri Jan 13 11:05:32 2017 Fri Jan 13 11:05:32 2017
Fri Jan 13 11:05:32 2017
func1 done at: Fri Jan 13 11:05:34 2017
func2 done at: Fri Jan 13 11:05:34 2017
从结果中可以看出t1,t2是同时启动的,也是同时完成的,完成两个函数只需要2s钟。但是这里也看到,在t1刚启动时,主线程就退出了,如果想让所有线程执行完再让主线程退出,那么可以用join函数,同时修改了func2执行时间为4s,修改后如下,
from threading import Thread
from time import sleep,ctime
def func1(nsec):
print('start func1 at:', ctime())
sleep(nsec)
print('func1 done at:', ctime())
def func2(nsec):
print('start func2 at:', ctime())
sleep(nsec)
print('func2 done at:', ctime())
def main():
threads = []
t1 = Thread(target=func1, args=(2, ))
threads.append(t1)
t2 = Thread(target=func2, args=(4, ))
threads.append(t2)
for t in threads:
t.start()
for t in threads:
t.join()
print('all Done at:', ctime())
if __name__ == '__main__':
main()
输出结果如下:
start func1 at:start func2 at: Fri Jan 13 11:28:12 2017
Fri Jan 13 11:28:12 2017
func1 done at: Fri Jan 13 11:28:14 2017
func2 done at: Fri Jan 13 11:28:16 2017
all Done at: Fri Jan 13 11:28:16 2017
可以看到,主线程是在两个子线程都完成后才退出的。
上面的代码也可以用面向对象的思想来从threading.Thread派生出一个MyThread类,代码如下
import threading
from time import sleep,ctime
class MyThread(threading.Thread):
def __init__(self, func, args, name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args
def run(self):
self.func(*self.args)
def getResult(self):
return self.res
def func1(nsec):
print('start func1 at:', ctime())
sleep(nsec)
print('func1 done at:', ctime())
def func2(nsec):
print('start func2 at:', ctime())
sleep(nsec)
print('func2 done at:', ctime())
def main():
threads = []
t1 = MyThread(func1, (2, ), func1.__name__)
threads.append(t1)
t2 = MyThread(func2, (4, ), func2.__name__)
threads.append(t2)
for t in threads:
t.start()
for t in threads:
t.join()
print('all Done at:', ctime())
if __name__ == '__main__':
main()
同步原语
我们模拟一个火车票售票程序,有两个线程在同时出售火车票,总共有10张票,每次卖出一张,当火车票数为0时,表示卖完了
from threading import Thread, Lock
from time import sleep,ctime
tickets = 10
lock = Lock()
def sellTikcets1():
global tickets
while (True):
sleep(1)
if (tickets > 0):
print('thread1 sell ticktes: ', tickets)
tickets -= 1
else:
break
print('thread1 exit')
return
def sellTikcets2():
global tickets
while (True):
sleep(1)
if (tickets > 0):
print('thread2 sell ticktes: ', tickets)
tickets -= 1
else:
break
print('thread2 exit')
return
def main():
threads = []
t1 = Thread(target=sellTikcets1, args=())
threads.append(t1)
t2 = Thread(target=sellTikcets2, args=())
threads.append(t2)
for t in threads:
t.start()
for t in threads:
t.join()
print('all Done at:', ctime())
if __name__ == '__main__':
main()
以上代码运行后输出:
thread2 sell ticktes: thread1 sell ticktes: 1010
thread2 sell ticktes: thread1 sell ticktes: 8
8
thread2 sell ticktes: thread1 sell ticktes: 66
thread2 sell ticktes: thread1 sell ticktes: 44
thread2 sell ticktes: thread1 sell ticktes: 2
2
thread2 exitthread1 exit
all Done at: Sat Jan 14 11:05:48 2017
发现有很大的问题,很明显在线程2打印输出后,还没有执行减一的操作,就转到了线程1,导致10号票卖出两次,没有了9号票。这就需要线程间的同步处理。
- Lock
应用Lock处理后,代码如下
from threading import Thread, Lock
from time import sleep,ctime
tickets = 10
lock = Lock()
def sellTikcets1():
global tickets
while (True):
sleep(1)
lock.acquire()
if (tickets > 0):
print('thread1 sell ticktes: ', tickets)
tickets -= 1
else:
break
lock.release()
lock.release()
print('thread1 exit')
return
def sellTikcets2():
global tickets
while (True):
sleep(1)
lock.acquire()
if (tickets > 0):
print('thread2 sell ticktes: ', tickets)
tickets -= 1
else:
break
lock.release()
lock.release()
print('thread2 exit')
return
def main():
threads = []
t1 = Thread(target=sellTikcets1, args=())
threads.append(t1)
t2 = Thread(target=sellTikcets2, args=())
threads.append(t2)
for t in threads:
t.start()
for t in threads:
t.join()
print('all Done at:', ctime())
if __name__ == '__main__':
main()
当线程1应用Lock对象的acquire()方法获得锁时,其他线程如果再想获取这个锁,就会阻塞,除非线程1用release()方法释放掉这个锁,这样就保证了上面的代码在访问tickets这个公共资源时,每次只有一个线程在访问。修改后的代码输出如下:
thread2 sell ticktes: 10
thread1 sell ticktes: 9
thread2 sell ticktes: 8
thread1 sell ticktes: 7
thread2 sell ticktes: 6
thread1 sell ticktes: 5
thread2 sell ticktes: 4
thread1 sell ticktes: 3
thread2 sell ticktes: 2
thread1 sell ticktes: 1
thread2 exitthread1 exit
all Done at: Sat Jan 14 11:12:40 2017
- Semaphores(信号量)
一个信号量实质上是管理了一个内部的计数器,通过release()方法使内部加1,acquire()方法使内部减一,一旦内部计数器为0时,如果调用acquire()方法方法,就会阻塞,同样,如果内部计数器达到设定值时,调用release()方法也会阻塞。经典的生产者消费者问题用信号量的实现如下(python核心编程中糖果生产销售的例子):
from atexit import register
from random import randrange
from threading import BoundedSemaphore, Lock, Thread
from time import sleep, ctime
lock = Lock()
MAX = 5
candytray = BoundedSemaphore(MAX)
def refill():
lock.acquire()
print('Refilling candy...')
try:
candytray.release()
except ValueError:
print('full, skipping')
else:
print('OK')
lock.release()
def buy():
lock.acquire()
print('Buying candy...')
if candytray.acquire(False):
print('OK')
else:
print('empty, skipping')
lock.release()
def producer(loops):
for i in range(loops):
refill()
sleep(randrange(3))
def comsumer(loops):
for i in range(loops):
buy()
sleep(randrange(3))
def main():
print('starting at:', ctime())
nloops = randrange(2,6)
print('THE CANDY MACHINE (full with %d bars)!' % MAX)
Thread(target=comsumer, args=(randrange(nloops,nloops+MAX+2),)).start()
Thread(target=producer, args=(nloops,)).start()
@register
def _atexit():
print('all DONE at:', ctime())
if __name__ == '__main__':
main()
这个例子应用了Lock和Semaphores来模拟这个过程:
starting at: Sat Jan 14 13:51:54 2017
THE CANDY MACHINE (full with 5 bars)!
Buying candy...
OK
Refilling candy...
OK
Buying candy...
OK
Refilling candy...
OK
Buying candy...
OK
Buying candy...
OK
Refilling candy...
OK
- 同步队列queue模块
queue模块提供了Queue(FIFO),LifoQueue(LIFO)和PriorityQueue(优先级队列)。put(item, block=True, timeout=None) 方法将item放入队列,如果block参数为True,调用者将被阻塞直到队列中出现可用的空闲位置为止,否则,队列为满会引发Full异常。
from random import randint
from time import sleep
from queue import Queue
from myThread import MyThread
def writeQ(queue):
print('producing object for Q...')
queue.put('xxx', 1)
print('size now', queue.qsize())
def readQ(queue):
val = queue.get(1)
print('consumed object from Q... size now', queue.qsize())
def writer(queue, loops):
for i in range(loops):
writeQ(queue)
sleep(randint(1, 3))
def reader(queue, loops):
for i in range(loops):
readQ(queue)
sleep(randint(2,5))
funcs = [writer, reader]
nfuncs = range(len(funcs))
def main():
nloops = randint(2, 5)
q = Queue(32)
threads = []
for i in nfuncs:
t = MyThread(funcs[i], (q, nloops), funcs[i].__name__)
threads.append(t)
for i in nfuncs:
threads[i].start()
for i in nfuncs:
threads[i].join()
print('all Done')
if __name__ == '__main__':
main()
multiprocessing模块
multiprocessing模块为在子进程中运行任务、通信和共享数据,以及执行各种形式的同步提供支持。与线程不同,进程没有任何共享状态,如果某个进程修改数据,改动只限于该进程内。该模块的编程接口都有意模仿threading模块中线程的编程接口。
- Process模块
Process模块的用法与threading的Thread类似,之前的打印的例子用Process实现如下
from threading import Thread
from time import sleep,ctime
from multiprocessing import Process
def func1(nsec):
print('start func1 at:', ctime())
sleep(nsec)
print('func1 done at:', ctime())
def func2(nsec):
print('start func2 at:', ctime())
sleep(nsec)
print('func2 done at:', ctime())
def main():
processes = []
t1 = Process(target=func1, args=(2, ))
processes.append(t1)
t2 = Process(target=func2, args=(4, ))
processes.append(t2)
for t in processes:
t.start()
for t in processes:
t.join()
print('all Done at:', ctime())
if __name__ == '__main__':
main()
- multiprocessing.Queue模块
生产者消费者问题用multiprocessing实现
from random import randint
from time import sleep
from multiprocessing import Process, Queue
def writeQ(queue):
print('producing object for Q...')
queue.put('xxx', 1)
print('size now', queue.qsize())
def readQ(queue):
val = queue.get(1)
print('consumed object from Q... size now', queue.qsize())
def writer(queue, loops):
for i in range(loops):
writeQ(queue)
sleep(randint(1, 3))
def reader(queue, loops):
for i in range(loops):
readQ(queue)
sleep(randint(2,5))
funcs = [writer, reader]
nfuncs = range(len(funcs))
def main():
nloops = randint(2, 5)
queue = Queue(3)
threads = []
for i in nfuncs:
t = Process(target = funcs[i], args = (queue, nloops))
threads.append(t)
for i in nfuncs:
threads[i].start()
for i in nfuncs:
threads[i].join()
queue.close()
print('all Done')
if __name__ == '__main__':
main()
- mutiprocessing.Pool,进程池
进程池示例
from multiprocessing import Pool
from time import ctime, sleep
def f(x):
sleep(1)
return x*x
if __name__ == '__main__':
pool = Pool(processes=5)
print('start at:', ctime())
print(pool.map(f, range(10)))
print('done at:', ctime())
输出:
start at: Sat Jan 14 15:32:54 2017
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
done at: Sat Jan 14 15:32:56 2017