并發(fā)編程 — — 多線程

# -*- coding: utf-8 -*-
# @Time    : 2019/11/28 16:42
# @Author  : John
# @Email   : 2398344557@qq.com
# @File    : 111.py
# @Software: PyCharm

當(dāng)使用單線程時(shí)袱衷,耗費(fèi)時(shí)間特長

【例】單線程

import time
def run(n):
    print("task ", n)
    time.sleep(2)

start_time = time.time()
run("1")
run("2")
print(f'執(zhí)行所消耗的時(shí)間:{time.time() - start_time}秒')

# task  1
# task  2
# 執(zhí)行所消耗的時(shí)間:4.00098729133606秒

執(zhí)行上面這個(gè)程序要4秒,如果用多線程的話种玛,函數(shù)“同時(shí)執(zhí)行”藐鹤,只需一半的時(shí)間即可H块堋!娱节!因此我們要引入多線程概念 ==>

【例】啟動(dòng)多個(gè)線程(函數(shù)方式)

import threading
import time

def run(n):
    print(f'任務(wù){(diào)n}')
    time.sleep(2)

# run('1')
# run('2')

start = time.time()

# t1和t2同時(shí)執(zhí)行
t1 = threading.Thread(target=run, args=('1',))
t2 = threading.Thread(target=run, args=('2',))
t1.start()
t2.start()

# 阻塞作用挠蛉,等t1和t2執(zhí)行完畢后再執(zhí)行下面的內(nèi)容
t1.join()
t2.join()

print('finished')
end = time.time()
print(f'此程序執(zhí)行時(shí)間為:{end-start}秒')

# 任務(wù)1
# 任務(wù)2
# finished
# 此程序執(zhí)行時(shí)間為:2.001213788986206秒

【例】啟動(dòng)多個(gè)線程(類方式)

import threading
import time


class MyThread(threading.Thread):
    def __init__(self, n, sleep_time):
        super().__init__()
        self.n = n
        self.sleep_time = sleep_time

    def run(self):
        print(f'任務(wù){(diào)self.n}開始')
        time.sleep(self.sleep_time)
        print(f'任務(wù){(diào)self.n}結(jié)束了')


t1 = MyThread(1, 1)    # 傳入n=1,sleep_time=1
t2 = MyThread(2, 2)    # 傳入n=2肄满,sleep_time=2

t1.start()
t2.start()

t1.join()
t2.join()

print('finished')

# 任務(wù)1開始
# 任務(wù)2開始
# 任務(wù)1結(jié)束了
# 任務(wù)2結(jié)束了
# finished

【例】同時(shí)啟動(dòng)20個(gè)線程

import threading
import time
def run(n):
    print(f'任務(wù){(diào)n}開始')
    time.sleep(2)
    print(f'任務(wù){(diào)n}結(jié)束了')

start_time = time.time()
for i in range(10):
    t = threading.Thread(target=run, args=(i,))
    t.start()

print("----------all threads has finished...")
print("消耗的時(shí)間:", time.time() - start_time)

# 結(jié)果略

# 分析:
# 主線程直接結(jié)束谴古,沒有等子線程,2s后子線程分別task done
# 代碼共有51個(gè)線程稠歉,一個(gè)主線程與50個(gè)子線程掰担,主線程無法計(jì)算子線程執(zhí)行時(shí)間
# 因此,我們需要設(shè)置主線程等待子線程執(zhí)行結(jié)束怒炸,通過一個(gè)臨時(shí)列表带饱,在線程啟動(dòng)后分別join等待,子線程分別結(jié)束后阅羹,結(jié)束主進(jìn)程勺疼,計(jì)算耗時(shí)約2.011415958s

【例】計(jì)算所有線程執(zhí)行時(shí)間

import threading
import time

def run(n):
    print(f'任務(wù){(diào)n}')
    time.sleep(2)
    print(f'任務(wù){(diào)n}已完成')

start_time = time.time()
t_objs = []  # 存線程實(shí)例
for i in range(10):
    t = threading.Thread(target=run, args=(i,))
    t.start()
    t_objs.append(t)  # 為了不阻塞后面線程的啟動(dòng),不在這里join灯蝴,先放到一個(gè)列表里

for t in t_objs:
    print(t.is_alive())

for t in t_objs:  # 循環(huán)線程實(shí)例列表恢口,等待所有線程執(zhí)行完畢
    t.join()

for t in t_objs:
    print(t.is_alive())    # is_alive()方法可以用來判斷一個(gè)線程是否結(jié)束,返回True或False

print("----------all threads has finished...")
print("消耗的時(shí)間:", time.time() - start_time)

結(jié)果如下:

任務(wù)0
任務(wù)1
任務(wù)2
任務(wù)3
任務(wù)4
任務(wù)5
任務(wù)6
任務(wù)7
任務(wù)8
任務(wù)9True
True
True

True
True
True
True
True
True
True
任務(wù)1已完成任務(wù)2已完成任務(wù)3已完成

任務(wù)0已完成

任務(wù)7已完成任務(wù)6已完成
任務(wù)5已完成
任務(wù)9已完成
任務(wù)4已完成

任務(wù)8已完成
False
False
False
False
False
False
False
False
False
False
----------all threads has finished...
消耗的時(shí)間: 2.12958025932312

【例】根據(jù)當(dāng)前線程(Thread)活著的數(shù)量來查看線程生命周期

import threading
import time

def sing():
    for i in range(3):
        print('正在唱歌穷躁。耕肩。。问潭。%d' % i)
        # time.sleep(random.random()*3)
        time.sleep(1)

def dance():
    for i in range(3):
        print('正在跳舞猿诸。。狡忙。梳虽。%d' % i)
        # time.sleep(random.random()*5)
        time.sleep(1)

if __name__=='__main__':
    # print('晚會(huì)開始:%s'%time.time())返回的是長串的時(shí)間戳
    print('晚會(huì)開始:%s' % time.ctime())
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()

    while True:
        length = len(threading.enumerate())    # 枚舉返回個(gè)列表
        print('當(dāng)前運(yùn)行的線程數(shù)為:%d' % length)
        time.sleep(0.7)
        if length <= 1:
            break

結(jié)果如下:

晚會(huì)開始:Sun Dec  8 17:12:54 2019
正在唱歌。灾茁。窜觉。。0
正在跳舞北专。禀挫。。拓颓。0
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:3
正在唱歌语婴。。。砰左。1
正在跳舞匿醒。。缠导。廉羔。1
當(dāng)前運(yùn)行的線程數(shù)為:3
正在唱歌。酬核。蜜另。适室。2
正在跳舞嫡意。。捣辆。蔬螟。2
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:1

多線程互斥鎖

作用:為了防止不同的線程訪問同一共享資源造成混亂

import threading
# 生成鎖對(duì)象,全局唯一

lock = threading.Lock()
# 獲取鎖汽畴。未獲取到會(huì)阻塞程序旧巾,直到獲取到鎖才會(huì)往下執(zhí)行
lock.acquire()
# 釋放鎖,歸回倘忍些,其他人可以拿去用了
lock.release()

# 注:lock.acquire() 和 lock.release()必須成對(duì)出現(xiàn)鲁猩,否則就有可能造成死鎖

# 可以使用使用上下文管理器來加鎖(常用)
# with 語句會(huì)在這個(gè)代碼塊執(zhí)行前自動(dòng)獲取鎖,在執(zhí)行結(jié)束后自動(dòng)釋放鎖
import threading
lock = threading.Lock()
with lock:
    pass

不上鎖的時(shí)候罢坝,對(duì)少量數(shù)據(jù)的修改有一定的作用廓握;
但對(duì)大量數(shù)據(jù)的修改,不上鎖的話會(huì)出現(xiàn)資源競爭問題嘁酿,從而數(shù)據(jù)結(jié)果會(huì)不正確

【例】測(cè)試1:未上鎖前隙券,用多線程對(duì)全局變量進(jìn)程修改

import threading
import time

g_num = 0

def work1(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("----in work1, g_num is %d---" % g_num)

def work2(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("----in work2, g_num is %d---" % g_num)

print("---線程創(chuàng)建之前g_num is %d---" % g_num)

t1 = threading.Thread(target=work1, args=(100,))
t1.start()

t2 = threading.Thread(target=work2, args=(100,))
t2.start()

while len(threading.enumerate()) != 1:
    time.sleep(1)

print("2個(gè)線程對(duì)同一個(gè)全局變量操作之后的最終結(jié)果是:%s" % g_num)
# ---線程創(chuàng)建之前g_num is 0---
# ----in work1, g_num is 100---
# ----in work2, g_num is 200---
# 2個(gè)線程對(duì)同一個(gè)全局變量操作之后的最終結(jié)果是:200

【例】測(cè)試2:未上鎖前,用多線程對(duì)全局變量進(jìn)程修改

import threading
import time

g_num = 0

def work1(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("----in work1, g_num is %d---" % g_num)

def work2(num):
    global g_num
    for i in range(num):
        g_num += 1
    print("----in work2, g_num is %d---" % g_num)

print("---線程創(chuàng)建之前g_num is %d---"%g_num)

t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()

t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()

while len(threading.enumerate()) != 1:
    time.sleep(1)

print("2個(gè)線程對(duì)同一個(gè)全局變量操作之后的最終結(jié)果是:%s" % g_num)
# ---線程創(chuàng)建之前g_num is 0---
# ----in work1, g_num is 1208926---
# ----in work2, g_num is 1264177---
# 2個(gè)線程對(duì)同一個(gè)全局變量操作之后的最終結(jié)果是:1264177

死鎖

在線程間共享多個(gè)資源的時(shí)候闹司,如果兩個(gè)線程分別占有一部分資源并且同時(shí)等待對(duì)方的資源娱仔,就會(huì)造成死鎖,一旦發(fā)生就會(huì)造成應(yīng)用的停止響應(yīng)游桩。

【例】死鎖

#coding=utf-8
import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 對(duì)mutexA上鎖
        mutexA.acquire()

        # mutexA上鎖后牲迫,延時(shí)1秒,等待另外那個(gè)線程 把mutexB上鎖
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此時(shí)會(huì)堵塞借卧,因?yàn)檫@個(gè)mutexB已經(jīng)被另外的線程搶先上鎖了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 對(duì)mutexA解鎖
        mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        # 對(duì)mutexB上鎖
        mutexB.acquire()

        # mutexB上鎖后盹憎,延時(shí)1秒,等待另外那個(gè)線程 把mutexA上鎖
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此時(shí)會(huì)堵塞谓娃,因?yàn)檫@個(gè)mutexA已經(jīng)被另外的線程搶先上鎖了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 對(duì)mutexB解鎖
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('done')
# Thread-1----do1---up----
# Thread-2----do2---up----
# (程序沒有結(jié)束)

# 分析:
# 對(duì)mutexA和mutexB同時(shí)上鎖脚乡,但沒有釋放他們時(shí),那就不能對(duì)他們繼續(xù)訪問,他們現(xiàn)在是阻塞狀態(tài)奶稠。

GIL(全局鎖)

  • 區(qū)分多進(jìn)程和多線程:
    (1)多進(jìn)程是真正意義上的并行俯艰,
    (2)而多線程只是偽并行(交替執(zhí)行)

  • 多線程偽并行(交替執(zhí)行)的原因:
    GIL(Global Interpreter Lock,全局解釋器鎖)

  • 分析:
    任何Python線程執(zhí)行前锌订,必須先獲得GIL鎖竹握,
    然后每執(zhí)行100條字節(jié)碼時(shí),解釋器就會(huì)自動(dòng)釋放GIL鎖辆飘,讓別的線程有機(jī)會(huì)執(zhí)行啦辐。
    而這個(gè)GIL全局鎖實(shí)際上把所有線程的執(zhí)行代碼都上鎖了。
    因此蜈项,多線程在Python中只能交替執(zhí)行芹关,
    即使100個(gè)線程拋在100核CPU上,也只能用到1個(gè)核紧卒。

  • 注:
    GIL并不是Python的特性侥衬,它是實(shí)現(xiàn)Python解釋器(CPython)時(shí)所引入的一個(gè)概念。
    而Python解釋器并不只有CPython跑芳,還有PyPy轴总,Psyco,JPython博个,IronPython等

  • 我們通常認(rèn)為Python == CPython怀樟,所以也就默許了Python具有GIL鎖

  • GIL影響性能,如何避免受到GIL影響盆佣?
    (1)使用多線程代替多線程(常用)
    (2)更換Python解釋器往堡,不使用CPython(不怎么用,因?yàn)镃Pthon挺好用的罪塔,真香~~)

Queue隊(duì)列

控制線程的觸發(fā)執(zhí)行 — — 用Queue隊(duì)列

格式:

from queue import  Queue
q = Queue(maxsize=0)    # maxsize默認(rèn)為0投蝉,此時(shí)隊(duì)列長度不受限

q.get()    # 等待隊(duì)列信息
q.get(timeout=5)    # 設(shè)置超時(shí)時(shí)間,時(shí)間到之后執(zhí)行其他
q.put()    # 發(fā)送信息
q.join()    # 等待所有的消息被消費(fèi)完

【例】生產(chǎn)者消費(fèi)者

import random
import threading
import time
import queue

q = queue.Queue(maxsize=10)    # 設(shè)置隊(duì)列的最大長度為10


# 生產(chǎn)者
def producer(name):
    count = 1
    while True:
        q.put("雪碧%s" % count)
        print("[Timmy]生產(chǎn)了雪碧", count)
        count += 1
        time.sleep(random.randrange(3))


# 消費(fèi)者
def consumer(name):
    while True:
        print("[%s]取到[%s]并且喝了它..." % (name, q.get()))
        time.sleep(random.randrange(5))


p = threading.Thread(target=producer, args=("Timmy",))
c1 = threading.Thread(target=consumer, args=("King",))
c2 = threading.Thread(target=consumer, args=("Wang",))

p.start()
c1.start()
c2.start()

結(jié)果如下:

[Timmy]生產(chǎn)了雪碧 1
[Timmy]生產(chǎn)了雪碧 2
[King]取到[雪碧1]并且喝了它...
[Wang]取到[雪碧2]并且喝了它...
[Timmy]生產(chǎn)了雪碧 3
[Wang]取到[雪碧3]并且喝了它...
[Timmy]生產(chǎn)了雪碧 4
[King]取到[雪碧4]并且喝了它...
[Timmy]生產(chǎn)了雪碧 5
[Wang]取到[雪碧5]并且喝了它...
[Timmy]生產(chǎn)了雪碧 6
[Timmy]生產(chǎn)了雪碧 7
[Wang]取到[雪碧6]并且喝了它...
[Wang]取到[雪碧7]并且喝了它...
# (程序沒有結(jié)束)

線程池

存儲(chǔ)線程征堪,需要用的時(shí)候調(diào)出線程
跑完任務(wù)之后瘩缆,這些線程不會(huì)被銷毀,而是返回到線程池等待下一次任務(wù)

  • submit():返回一個(gè)future對(duì)象
  • future對(duì)象:在未來的某一時(shí)刻完成操作的對(duì)象

【例】創(chuàng)建線程池

import time
from concurrent.futures.thread import ThreadPoolExecutor

# 線程執(zhí)行的函數(shù)
def add(n1,n2):
    v = n1 + n2
    print('add :', v, ', tid:', threading.currentThread().ident)
    time.sleep(n1)
    return v

# 通過submit把需要執(zhí)行的函數(shù)扔進(jìn)線程池中.
# submit 直接返回一個(gè)future對(duì)象
ex = ThreadPoolExecutor(max_workers=3)    # 制定最多運(yùn)行N個(gè)線程
f1 = ex.submit(add, 2, 3)
f2 = ex.submit(add, 2, 2)
print('main thread running')
print(f1.done())    # future對(duì)象名.done():看看任務(wù)結(jié)束了沒
print(f2.done())
print(f1.result())    # future對(duì)象名.result():獲取結(jié)果 ,阻塞方法
print(f2.result())
print(f2.done())
print(f1.done())
# add : 5 , tid: 6324
# add : 4 , tid: 6712
# main thread running
# False
# False
# 5
# 4
# True
# True
  • map():返回是跟你提交序列是一致的佃蚜,是有序的

【例】map()的使用

import requests
from concurrent.futures.thread import ThreadPoolExecutor

URLS = ['http://www.sina.com.cn', 'http://www.baidu.com', 'http://www.qq.com']

def get_html(url):
    print('thread id:', threading.currentThread().ident, ' 訪問了:', url)
    return requests.get(url)    # 這里使用了requests 模塊

ex = ThreadPoolExecutor(max_workers=3)    # 制定最多運(yùn)行3個(gè)線程
# thread id: 5596  訪問了: http://www.sina.com.cn
# thread id: 8968  訪問了: http://www.baidu.com
# thread id: 4316  訪問了: http://www.qq.com

# sumbit()函數(shù):
lst = []
for i in range(3):
    f = ex.submit(get_html, URLS[i])    # f為future對(duì)象庸娱,提交一個(gè)任務(wù),放入線程池中,準(zhǔn)備執(zhí)行
    lst.append(f)
print(lst)
# [<Future at 0x1330990 state=running>, <Future at 0x3015370 state=running>, <Future at 0x3015870 state=running>]

# map()函數(shù):
res_iter = ex.map(get_html, URLS)    # 返回生成器res_iter
for res in res_iter:
    print(res.url)
# 分別獲取sina、baidu谐算、qq的網(wǎng)頁網(wǎng)址
# https://www.sina.com.cn/
# http://www.baidu.com/
# https://www.qq.com/
for res in res_iter:
    print(res.text)
# 分別獲取sina熟尉、baidu、qq的網(wǎng)頁源代碼
# 略

# - as_completed()函數(shù):
from concurrent.futures import as_completed
    # 下面用到的f洲脂,是上面第一個(gè)循環(huán)里面的futrue對(duì)象
    for future in as_completed([f]):    # as_completed()接受一個(gè)可迭代的Future序列斤儿,返回一個(gè)生成器,在完成或異常時(shí)返回這個(gè)Future對(duì)象
        print('一個(gè)任務(wù)完成剧包。')
        print(future.result())
# thread id: 5484  訪問了: http://www.sina.com.cn
# 一個(gè)任務(wù)完成。
# <Response [200]>
# thread id: 5484  訪問了: http://www.baidu.com
# 一個(gè)任務(wù)完成往果。
# <Response [200]>
# thread id: 10280  訪問了: http://www.qq.com
# 一個(gè)任務(wù)完成疆液。
# <Response [200]>

【例】as_completed 完整的例子

# as_completed 返回一個(gè)生成器,用于迭代陕贮, 一旦一個(gè)線程完成(或失敗) 就返回
import time
import requests
from concurrent.futures.thread import ThreadPoolExecutor
from concurrent.futures import as_completed

URLS = ['http://www.baidu.com', 'http://www.qq.com', 'http://www.sina.com.cn']
def get_html(url):
    time.sleep(1)
    print('thread id:', threading.currentThread().ident,' 訪問了:', url)
    return requests.get(url)    # 這里使用了requests 模塊
ex = ThreadPoolExecutor(max_workers=3)    # 最多3個(gè)線程
future_tasks = [ex.submit(get_html, url) for url in URLS]    #創(chuàng)建3個(gè)future對(duì)象
for future in as_completed(future_tasks):    # 迭代生成器
    try:
        resp = future.result()
    except Exception as e:
        print('%s' % e)
    else:
        print('%s has %d bytes!' % (resp.url, len(resp.text)))

# thread id: 4912  訪問了: http://www.sina.com.cn
# thread id: 9060  訪問了: http://www.qq.com
# thread id: 5644  訪問了: http://www.baidu.com
# http://www.baidu.com/ has 2381 bytes!
# https://www.qq.com/ has 223356 bytes!
# https://www.sina.com.cn/ has 543959 bytes!

wait:阻塞函數(shù)

第一個(gè)參數(shù)和as_completed一樣堕油,一個(gè)可迭代的future序列,返回一個(gè)元組,包含2個(gè)set肮之,一個(gè)完成的掉缺,一個(gè)未完成的

【例】wait()函數(shù)的使用

import time
import requests
from concurrent.futures import wait, as_completed, FIRST_COMPLETED, ALL_COMPLETED
from concurrent.futures.thread import ThreadPoolExecutor
import threading
from concurrent import futures

URLS = ['http://www.baidu.com', 'http://www.qq.com', 'http://www.sina.com.cn']
def get_html(url):
    """
    FIRST_COMPLETED    當(dāng)任何未來完成或被取消時(shí),該函數(shù)將返回戈擒。
    FIRST_EXCEPTION    當(dāng)任何未來通過提出異常完成時(shí)眶明,函數(shù)將返回。如果沒有未來引發(fā)異常峦甩,那么它等同于 ALL_COMPLETED赘来。
    ALL_COMPLETED(默認(rèn))      當(dāng)所有期貨完成或被取消時(shí)现喳,函數(shù)將返回凯傲。
    :param url:
    :return:
    """
    time.sleep(1)
    print('thread id:', threading.currentThread().ident, ' 訪問了:', url)
    return requests.get(url)    # 這里使用了requests 模塊
ex = ThreadPoolExecutor(max_workers=3)    # 最多3個(gè)線程
future_tasks = [ex.submit(get_html, url) for url in URLS]    # 創(chuàng)建3個(gè)future對(duì)象
try:
    result = wait(future_tasks, return_when=futures.ALL_COMPLETED)
    done_set = result[0]
    for future in done_set:
        resp = future.result()
        print('第一個(gè)網(wǎng)頁任務(wù)完成 url:%s , len:%d bytes! ' % (resp.url, len(resp.text)))
except Exception as e:
    print('exception :', e)

# return_when=futures.FIRST_COMPLETED
# thread id: 11404  訪問了: http://www.sina.com.cn
# thread id: 10196  訪問了: http://www.baidu.com
# thread id: 2588  訪問了: http://www.qq.com
# 第一個(gè)網(wǎng)頁任務(wù)完成 url:http://www.baidu.com/ , len:2381 bytes!

# return_when=futures.FIRST_EXCEPTION
# thread id: 4776  訪問了: http://www.sina.com.cn
# thread id: 12104  訪問了: http://www.baidu.com
# thread id: 10692  訪問了: http://www.qq.com
# 第一個(gè)網(wǎng)頁任務(wù)完成 url:https://www.sina.com.cn/ , len:544154 bytes!
# 第一個(gè)網(wǎng)頁任務(wù)完成 url:https://www.qq.com/ , len:223122 bytes!
# 第一個(gè)網(wǎng)頁任務(wù)完成 url:http://www.baidu.com/ , len:2381 bytes!

# return_when=futures.ALL_COMPLETED
# thread id: 4612  訪問了: http://www.sina.com.cn
# thread id: 11460  訪問了: http://www.qq.com
# thread id: 7744  訪問了: http://www.baidu.com
# 第一個(gè)網(wǎng)頁任務(wù)完成 url:http://www.baidu.com/ , len:2381 bytes!
# 第一個(gè)網(wǎng)頁任務(wù)完成 url:https://www.sina.com.cn/ , len:544154 bytes!
# 第一個(gè)網(wǎng)頁任務(wù)完成 url:https://www.qq.com/ , len:223122 bytes!

回調(diào)函數(shù):add_done_callback(fn)

import os, sys, time, requests, threading
from concurrent import futures

URLS = [
        'http://baidu.com',
        'http://www.qq.com',
        'http://www.sina.com.cn'
        ]

def load_url(url):
    print('tid:', threading.currentThread().ident, ',url:', url)
    with requests.get(url) as resp:
        return resp.content
def call_back(obj):
    print('->>>>>>>>>call_back , tid:', threading.currentThread().ident, ',obj:', obj)

with futures.ThreadPoolExecutor(max_workers=3) as ex:
    # mp = {ex.submit(load_url,url) : url for url in URLS}
    mp = dict()
    for url in URLS:
        f = ex.submit(load_url, url)
        mp[f] = url
        f.add_done_callback(call_back)
    for f in futures.as_completed(mp):
        url = mp[f]
        try:
            data = f.result()
        except Exception as exc:
            print(exc, ',url:', url)
        else:
            print('url:', url, ',len:', len(data), ',data[:20]:', data[:20])

# tid: 6036 ,url: http://baidu.com
# tid: 6044 ,url: http://www.qq.com
# tid: 3864 ,url: http://www.sina.com.cn
# ->>>>>>>>>call_back , tid: 6036 ,obj: <Future at 0x2f076d0 state=finished returned bytes>
# url: http://baidu.com ,len: 81 ,data[:20]: b'<html>\n<meta http-eq'
# ->>>>>>>>>call_back , tid: 6044 ,obj: <Future at 0x30158b0 state=finished returned bytes>
# url: http://www.qq.com ,len: 237492 ,data[:20]: b'<!doctype html>\n<htm'
# ->>>>>>>>>call_back , tid: 3864 ,obj: <Future at 0x3015c70 state=finished returned bytes>
# url: http://www.sina.com.cn ,len: 544154 ,data[:20]: b'<!DOCTYPE html>\n<!--'
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嗦篱,隨后出現(xiàn)的幾起案子冰单,更是在濱河造成了極大的恐慌,老刑警劉巖灸促,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诫欠,死亡現(xiàn)場離奇詭異,居然都是意外死亡浴栽,警方通過查閱死者的電腦和手機(jī)荒叼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來典鸡,“玉大人被廓,你說我怎么就攤上這事÷茜瑁” “怎么了嫁乘?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長球碉。 經(jīng)常有香客問我蜓斧,道長,這世上最難降的妖魔是什么睁冬? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任挎春,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘直奋。我一直安慰自己狼荞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布帮碰。 她就那樣靜靜地躺著相味,像睡著了一般。 火紅的嫁衣襯著肌膚如雪殉挽。 梳的紋絲不亂的頭發(fā)上丰涉,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音斯碌,去河邊找鬼一死。 笑死,一個(gè)胖子當(dāng)著我的面吹牛傻唾,可吹牛的內(nèi)容都是我干的投慈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼冠骄,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼伪煤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凛辣,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤抱既,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扁誓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體防泵,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年蝗敢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捷泞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寿谴,死狀恐怖锁右,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拭卿,我是刑警寧澤骡湖,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站峻厚,受9級(jí)特大地震影響响蕴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惠桃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一浦夷、第九天 我趴在偏房一處隱蔽的房頂上張望辖试。 院中可真熱鬧,春花似錦劈狐、人聲如沸罐孝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莲兢。三九已至,卻和暖如春续膳,著一層夾襖步出監(jiān)牢的瞬間改艇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工坟岔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谒兄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓社付,卻偏偏與公主長得像承疲,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸥咖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容