Python多线程

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值