python多线程模块(threading、queue、concurrent.futures)学习笔记

一、多线程支持模块:

  • _thread(不推荐使用)
  • threading
  • queue

二、threading模块的对象:

对象 描述
Thread 表示一个执行线程的对象
Lock 锁原语对象(互斥锁)
RLock 可重入锁对象,单一线程可以获得已持有的锁(递归锁)
Condition 条件变量对象,使得一个线程等待另一个线程满足特定的“条件”
Event 任意数量线程等待某个事件的发生,该事件发生后所有等待该事件的线程将激活
Semaphore 为线程间共享的优先资源提供一个“计数器”,如果没有可用资源时会被阻塞
BoundedSemaphore 和Semaphore相似,不过不允许超过初始值
Timer 和Thread相似,不过运行前会等待一段时间
Barrier 创建一个障碍,必须达到指定数量线程才开始运行

1.Thread类:

Thread对象的属性和方法:
属性 描述
对象数据属性
name 线程名
ident 线程的标识符
daemon 布尔标志,表示这个线程是否是守护线程
Thread对象方法
init(group=None, tatget=None, name=None, args=(),kwargs={}, verbose=None, daemon=None) 实例化一个线程对象,需要有一个可调用的target,以及其参数args或kwargs。还可以传递name或group参数,不过后者还没实现。此外,verbose标志也是可接受的,而daemon的值将会设定thread.daemon标志
start() 开始执行该线程
run() 定义线程功能的方法(通常在子类中被应用开发者重写)
join(timeout=None) 直到启动的线程终止之前一直挂起;除非给出timeout(秒)否则会一直阻塞
isAlivel() 布尔标志,表示这个线程是否还存活
创建线程的方法:
1.创建Thread的实例,传给它一个函数

范例:

import threading
from time import sleep, ctime

loops = [4,2]

def loop(nloop, nsec):
    print("start loop " + str(nloop) + " at: " + str(ctime()))
    sleep(nsec)
    print("loop " + str(nloop) + " done at: " + str(ctime()))

if __name__=="__main__":
    print("starting at: " + str(ctime()))
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = threading.Thread(target=loop,args=(i, loops[i]))
        threads.append(t)

    for i in nloops:
        threads[i].start()

    for i in nloops:
        threads[i].join()

    print("all DONE at: " + str(ctime()))
2.创建Thread的实例,传给它一个可调用的类实例

范例:

import threading
from time import sleep, ctime

loops = [4,2]

class ThreadFunc(object):
    def __init__(self, func, args, name=" "):
        self.name = name
        self.func = func
        self.args = args

    def __call__(self):
        self.func(*self.args)

def loop(nloop, nsec):
    print("start loop " + str(nloop) + " at: " + str(ctime()))
    sleep(nsec)
    print("loop " + str(nloop) + " done at: " + str(ctime()))

if __name__=="__main__":
    print("starting at: " + str(ctime()))
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__))
        threads.append(t)

    for i in nloops:
        threads[i].start()

    for i in nloops:
        threads[i].join()

    print("all DONE at: " + str(ctime()))
3.派生Thread的子类,并创建子类的实例

范例:

import threading
from time import sleep, ctime

loops = (4,2)

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 loop(nloop, nsec):
    print("Start loop " + str(nloop) + " at:",ctime())
    sleep(nsec)
    print("loop " + str(nloop) + " done at:",ctime())
    
if __name__ == "__main__":
    print("starting at:", ctime())
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = MyThread(loop, (i, loops[i]), loop.__name__)
        threads.append(t)

    for i in nloops:
        threads[i].start()

    for i in nloops:
        threads[i].join()

    print("all DONE at:", ctime())

2.threading模块的其他函数

函数对象 描述
activeCount() 返回当前活动的Thread对象个数
currentThread() 返回当前的Thread对象
enumerate() 返回当前活动的Thread对象列表
settrace(func) 为所有线程设置一个trace函数
setprofile(func) 为所有线程设置一个profile函数
stack_size(size=0) 返回新创建的线程的栈大小;或为后续创建线程设定栈的大小为size

三、同步

1.锁

锁的两种状态:锁定和未锁定

锁对象:Lock(或RLock)

获得锁:acquire()

释放锁:release()

多线程争夺锁时,允许第一个获得锁的线程进入临界区,并执行代码。之后所有到达的线程将被阻塞,直到第一个线程执行结束,退出临界区,并释放锁。此时其他等待的线程中随机一个(可根据Python实现的不同而有所区别)可以获得锁并进入临界区。

范例:

from atexit import register
from random import randrange
from threading import Thread, Lock, currentThread
from time import sleep, ctime

class CleanOutputSet(set):
    def __str__(self):
        return ", ".join(x for x in self)

lock = Lock()
loops = (randrange(2,5) for x in range(randrange(3,7)))
remaining = CleanOutputSet()

def loop(nsec):
    myname = currentThread().name
    lock.acquire()
    remaining.add(myname)
    print("[%s] Started %s" % (ctime(), myname))
    lock.release()
    sleep(nsec)
    lock.acquire()
    remaining.remove(myname)
    print("[%s] Completed %s (%d secs)" % (ctime(), myname, nsec))
    print("    (remaining: %s)" % (remaining or "NONE"))
    lock.release()

if __name__ == "__main__":
    for pause in loops:
        Thread(target=loop, args=(pause,)).start()

@register
def _atexit():
    print("all DONE at: ",ctime())

上下文管理:

使用with语句,此时每个上下文管理器负责在进入该语句块前调用acquire()并在执行之后调用release()

范例:

from atexit import register
from random import randrange
from threading import Thread, Lock, currentThread
from time import sleep, ctime

class CleanOutputSet(set):
    def __str__(self):
        return ", ".join(x for x in self)

lock = Lock()
loops = (randrange(2,5) for x in range(randrange(3,7)))
remaining = CleanOutputSet()

def loop(nsec):
    myname = currentThread().name
    with lock:
        remaining.add(myname)
        print("[%s] Started %s" % (ctime(), myname))

    sleep(nsec)
    with lock:
        remaining.remove(myname)
        print("[%s] Completed %s (%d secs)" % (ctime(), myname, nsec))
        print("    (remaining: %s)" % (remaining or "NONE"))

if __name__ == "__main__":
    for pause in loops:
        Thread(target=loop, args=(pause,)).start()

@register
def _atexit():
    print("all DONE at: ",ctime())

2.信号量

信号量是一个计数器,当资源消耗时递减,资源释放时递增,如果没有可用资源时消耗资源会被阻塞。

信号量对象:Semaphore(或BoundedSemaphore)

资源释放:release()

资源消耗:acquire()

范例:

#使用锁和信号量来模拟一个糖果机
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():
    with lock:
        print("Refilling candy...")
        try:
            candytray.release()
        except ValueError:
            print("full, skipping")
        else:
            print("OK")

def buy():
    with lock:
        print("Buying candy...")
        if candytray.acquire(False):
            print("OK")
        else:
            print("Empty, skipping")

def produce(loops):
    for i in range(loops):
        refill()
        sleep(randrange(3))

def consumer(loops):
    for i in range(loops):
        buy()
        sleep(randrange(3))

if __name__ == "__main__":
    print("Starting at: ", ctime())
    nloops = randrange(2, 6)
    print("THE CANDY MACHINE (full with %d bars)!" % MAX)
    Thread(target=consumer, args=(randrange(nloops, nloops+MAX+2),)).start()
    Thread(target=produce, args=(nloops,)).start()

@register
def _atexit():
    print("all DONE at: ",ctime())

四、queue模块

1.queue模块

常用属性:

属性 描述
Queue(maxsize=0) 创建一个先入先出的队列。如果给定最大值,则在队列没有空间时阻塞;否则为无限队列
LifoQueue(maxsize=0) 创建一个后入先出的队列。如果给定最大值,则在队列没有空间时阻塞;否则为无限队列
PriorityQueue(maxsize=0) 创建一个优先级队列。如果给定最大值,则在队列没有空间时阻塞;否则为无限队列
异常
Empty 当对空队列调用get*()方法时抛出异常
Full 当对已满的队列调用put*()方法时抛出异常
queue对象方法
qsize() 返回队列大小(由于返回时队列大小可能被其他线程修改,所以该值为近似值)
empty() 如果队列为空,则返回True;否则,返回False
full() 如果队列为满,则返回True;否则,返回False
put(item, block=True,timeout=None) 将item放入队列。如果block为True(默认)且timeout为 None,则在有可用空间之前阻塞;如果timeout为正值,则最多阻塞timeout秒;如果block为False,则抛出Empty异常
put_nowait(item) 和put(item, False)相同
get(block=True, timeout=None) 从对列中取得元素。如果给定了block(非0),则一直阻塞到有可用的元素为止
get_nowait() 和get(False)相同
task_done() 用于表示对列中某个元素已执行完成,该方法会被下面的join()使用
join() 在队列中所有元素执行完毕并调用上面的task_done()信号之前,保持阻塞

2.队列范例及生产者-消费者问题

import threading
from random import randint
from time import sleep, ctime
from queue import Queue

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):
        print("starting",self.name,"at:",ctime())
        self.res = self.func(*self.args)
        print(self.name, "finished at:",ctime())

    def getResult(self):
        return self.res

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))

if __name__ == "__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")

五、高级线程模块concurrent.futures

concurrent.futures主要用于管理并发任务池。

常用属性:

属性 描述
Executor 抽象类。提供异步执行调用方法。要通过它的子类调用,而不是直接调用。
ThreadPoolExecutor(max_workers=None, thread_name_prefix=“”, initializer=None, initargs=()) 是 Executor 的子类,使用线程池来异步执行调用。使用最多 max_workers 个线程的线程池来异步执行调用。initializer 是在每个工作者线程开始处调用的一个可选可调用对象。initargs 是传递给初始化器的元组参数。任何向池提交更多工作的尝试, initializer 都将引发一个异常,当前所有等待的工作都会引发一个 BrokenThreadPool
Future 将可调用对象封装为异步执行。Future 实例由 Executor.submit() 创建
异常
CancelledError future 对象被取消时会触发
TimeoutError future 对象执行超出给定的超时数值时引发
BrokenExecutor 派生于 RuntimeError 的异常类,当执行器被某些原因中断而且不能用来提交或执行新任务时就会被引发。
BrokenThreadPool 当 ThreadPoolExecutor 中的其中一个工作者初始化失败时会引发派生于 BrokenExecutor 的异常类
Executor对象方法
submit(fn, *args, **kwargs) 调度可调用对象 fn,以 fn(*args **kwargs) 方式执行并返回 Future 对像代表可调用对象的执行
map(func, *iterables, timeout=None, chunksize=1) 将 iterable 参数传入的可迭代对象func传递给不同的线程来处理,返回所有结果收集后的可迭代对象。如果从原始调用到 Executor.map() 经过 timeout 秒后, next() 已被调用且返回的结果还不可用,那么已返回的迭代器将触发 concurrent.futures.TimeoutError 。 timeout 可以是整数或浮点数。如果 timeout 没有指定或为 None ,则没有超时限制。如果 *func调用引发一个异常,当从迭代器中取回它的值时这个异常将被引发
shutdown(wait=True) 当待执行的 future 对象完成执行后向执行者发送信号,它就会释放正在使用的任何资源。 在关闭后调用 Executor.submit() 和 Executor.map() 将会引发 RuntimeError。如果 wait 为 True 则此方法只有在所有待执行的 future 对象完成执行且释放已分配的资源后才会返回。 如果 wait 为 False,方法立即返回,所有待执行的 future 对象完成执行后会释放已分配的资源。 不管 wait 的值是什么,整个 Python 程序将等到所有待执行的 future 对象完成执行后才退出。
Future 对象方法
result(timeout=None) 返回调用返回的值。如果调用还没完成那么这个方法将等待 timeout 秒。如果在 timeout 秒内没有执行完成,concurrent.futures.TimeoutError 将会被触发。timeout 可以是整数或浮点数。如果 timeout 没有指定或为 None,那么等待时间就没有限制。如果 futrue 在完成前被取消则 CancelledError 将被触发。如果调用引发了一个异常,这个方法也会引发同样的异常。
done() 如果调用已被取消或正常结束那么返回 True
add_done_callback(fn) 附加可调用 fn 到 future 对象。当 future 对象被取消或完成运行时,将会调用 fn,而这个 future 对象将作为它唯一的参数。加入的可调用对象总被属于添加它们的进程中的线程按加入的顺序调用。如果可调用对象引发一个 Exception 子类,它会被记录下来并被忽略掉。如果可调用对象引发一个 BaseException 子类,这个行为没有定义。如果 future 对象已经完成或已取消,fn 会被立即调用
模块函数
wait(fs, timeout=None, return_when=ALL_COMPLETED) 等待 fs 指定的 Future 实例(可能由不同的 Executor 实例创建)完成。 返回一个由集合构成的具名 2 元组。 第一个集合名称为 done,包含在等待完成之前已完成的期程(包括正常结束或被取消的 future 对象)。 第二个集合名称为 not_done,包含未完成的 future 对象(包括挂起的或正在运行的 future 对象)。
timeout 可以用来控制返回前最大的等待秒数。 timeout 可以为 int 或 float 类型。 如果 timeout 未指定或为 None ,则不限制等待时间。
return_when 指定此函数应在何时返回。它必须为以下常数之一:FIRST_COMPLETED(函数将在任意可等待对象结束或取消时返回)、FIRST_EXCEPTION(函数将在任意可等待对象因引发异常而结束时返回。当没有引发任何异常时它就相当于 ALL_COMPLETED)、ALL_COMPLETED(函数将在所有可等待对象结束或取消时返回)
as_completed(fs, timeout=None) 返回一个包含 fs 所指定的 Future 实例(可能由不同的 Executor 实例创建)的迭代器,这些实例会在完成时生成 future 对象(包括正常结束或被取消的 future 对象)。 任何由 fs 所指定的重复 future 对象将只被返回一次。 任何在 as_completed() 被调用之前完成的 future 对象将优先被生成。 如果 next() 被调用并且在对 as_completed() 的原始调用 timeout 秒之后结果仍不可用,则返回的迭代器将引发 concurrent.futures.TimeoutError。 timeout 可以为整数或浮点数。 如果 timeout 未指定或为 None,则不限制等待时间。

范例:

from concurrent.futures import ThreadPoolExecutor
import time

# 参数times用来test的时间
def test(times):
    time.sleep(times)
    print("test {}s finished".format(times))
    return times

executor = ThreadPoolExecutor(max_workers=2)
# 通过submit函数提交执行的函数到线程池中,submit函数立即返回,不阻塞
task1 = executor.submit(test, (3))
task2 = executor.submit(test, (2))
# done方法用于判定某个任务是否完成
print(task1.done())
time.sleep(4)
print(task1.done())
# result方法可以获取task的执行结果
print(task1.result())
  • ThreadPoolExecutor构造实例的时候,传入max_workers参数来设置线程池中最多能同时运行的线程数目。

  • 使用submit函数来提交线程需要执行的任务(函数名和参数)到线程池中,并返回该任务的句柄(future对象),submit()不阻塞立即返回

  • 通过submit函数返回的future对象,能够使用done()方法判断该任务是否结束。上面的例子可以看出,由于任务有2s的延时,在task1提交后立刻判断,task1还未完成,而在延时4s之后判断,task1就完成了

  • 使用result()方法可以获取任务的返回值。查看内部代码,发现这个方法是阻塞的

范例2:

from concurrent.futures import ThreadPoolExecutor
import time

# 参数times用来test的时间
def test(times):
    time.sleep(times)
    print("test {}s finished".format(times))
    return times

executor = ThreadPoolExecutor(max_workers=2)
ns = [3, 2, 4]

for data in executor.map(test, ns):
    print("test {}s success".format(data))
  • map方法与python标准库中的map含义相同,都是将序列中的每个元素都执行同一个函数。上面的代码就是对ns的每个元素都执行test函数,并分配线程。可以看到输出顺序和urls列表的顺序相同。

范例3:

from concurrent.futures import ThreadPoolExecutor, as_completed
import time

# 参数times用来test的时间
def test(times):
    time.sleep(times)
    print("test {}s finished".format(times))
    return times

executor = ThreadPoolExecutor(max_workers=2)
ns = [3, 2, 4]
all_task = [executor.submit(test, (n)) for n in ns]

for future in as_completed(all_task):
    data = future.result()
    print("in main: test {}s success".format(data))
  • as_completed()方法是一个生成器,在没有任务完成的时候,会阻塞,在有某个任务完成的时候,会yield这个任务,就能执行for循环下面的语句,然后继续阻塞住,循环到所有的任务结束。从结果也可以看出,先完成的任务会先通知主线程。

范例4:

from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, FIRST_COMPLETED
import time

# 参数times用来test的时间
def test(times):
    time.sleep(times)
    print("get page {}s finished".format(times))
    return times

executor = ThreadPoolExecutor(max_workers=2)
ns = [3, 2, 4]
all_task = [executor.submit(test, (n)) for n in ns]
wait(all_task, return_when=ALL_COMPLETED)
print("main")
  • wait方法接收3个参数,等待的任务序列、超时时间以及等待条件。等待条件return_when默认为ALL_COMPLETED,表明要等待所有的任务都结束。可以看到运行结果中,确实是所有任务都完成了,主线程才打印出main。

concurrent.futures部分4个范例参考自https://www.jianshu.com/p/b9b3d66aa0be

案例5:

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {
    
    executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

猜你喜欢

转载自blog.csdn.net/Qingyuyuehua/article/details/114296245