python3 結束子線程


  最近公司內部網絡經常出問題,奇慢無比,導致人臉檢測程序在下載圖片時經常卡住,為了不影響數據的核對, 決定在網絡不佳圖片下載超時后放棄下載,繼續執行后續程序。

於是整理出解決思路如下:

  1、在線程中完成圖片下載任務

  2、設置圖片下載超時的時間

  3、當下載超時后線束下載線程, 執行后續任務

為了便於演示下載效果, 決定采集requests請求方法, 而不用urltrieve下載

 

一、先看看單線程如何下載圖片的問題

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# __author__:kzg import threading import time
from urllib.request import urlretrieve def callbackinfo(down, block, size): ''' 回調函數: down:已經下載的數據塊 block:數據塊的大小 size:遠程文件的大小 ''' per = 100.0 * (down * block) / size if per > 100: per = 100 time.sleep(1) # sleep 1秒 print('%.2f%%' % per)
# 圖片下載函數 def downpic(url): urlretrieve(url,
'test.jpg', callbackinfo) url = 'https://s1.tuchong.com/content-image/201909/98cac03c4a131754ce46d51faf597230.jpg' # 執行線程 t = threading.Thread(target=downpic, args=(url,)) t.start() t.join(3) print("down OK") 結果: 0.00% 1.51% down OK 3.02% 4.52% 6.03%
……

  可以看到,執行過程

    1、將圖片下載程序塞到線程中執行

    2、啟動線程

    3、三秒后線程仍未執行完,放棄阻塞

    4、執行print

    5、線程繼續執行, 直到完成

 

二、守護線程(deamon)

    守護線程結束, 其中的子線程也被迫結束

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# __author__:kzg

import threading
import time
from urllib.request import urlretrieve

def callbackinfo(down, block, size):
    '''
    回調函數:
    down:已經下載的數據塊
    block:數據塊的大小
    size:遠程文件的大小
    '''
    per = 100.0 * (down * block) / size
    if per > 100:
        per = 100
    time.sleep(1)
    print('%.2f%%' % per)

def downpic(url):
    urlretrieve(url, 'test.jpg', callbackinfo)

def mainFunc(funcname, args):
    '''
    :param funcname: 函數名(圖片下載函數)
    :param args: 參數(url地址)
    :return:
    '''
    t = threading.Thread(target=funcname, args=(args,))
    t.start()  # 開始執行線程
    t.join(timeout=5)  # 5秒后線程仍未執行完則放棄阻塞, 繼續執行后續代碼


url = 'https://s1.tuchong.com/content-image/201909/98cac03c4a131754ce46d51faf597230.jpg'

m = threading.Thread(target=mainFunc, args=(downpic, url))
m.setDaemon(True)
m.start()
m.join()


結果:
0.00%
1.51%
3.02%
4.52%

  可以看到執行結果:

    1、mainfunc函數被塞到m線程中

    2、m線程設置為守護線程

    3、啟動守護線程

    4、mainfunc下的子線程 t在5秒后仍未執行完, 

        放棄阻塞,執行后續程序

        m.join被執行, 守護線程結束,子線程t 被迫結束(結果中只有圖片只下載了4秒) 

        圖片中止下載

 

  按說到此為止應該圓滿結束了, 然而在程序執行過程中發現子線程超時后, 確實開始執行后續代碼,但子線程並未退出,仍然在運行。 經過不斷排查發現問題出現在for循環上, 原來for循環也類似一個demon的線程,如果for循環一直不結束, 其內的子線程就不會結束。

 

三、遇到問題, 子線程未被關閉

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# __author__:kzg

import threading
import time
from urllib.request import urlretrieve

def callbackinfo(down, block, size):
    '''
    回調函數:
    down:已經下載的數據塊
    block:數據塊的大小
    size:遠程文件的大小
    '''
    per = 100.0 * (down * block) / size
    if per > 100:
        per = 100
    time.sleep(1)
    print('%.2f%%' % per)

# 圖片下載函數
def downpic(url):
    urlretrieve(url, 'test.jpg', callbackinfo)

def mainFunc(funcname, args):
    '''
    :param funcname: 函數名(圖片下載函數)
    :param args: 參數(url地址)
    :return:
    '''
    t = threading.Thread(target=funcname, args=(args,))
    t.start()  # 開始執行線程
    t.join(timeout=5)  # 3秒后線程仍未執行完則放棄阻塞, 繼續執行后續代碼

for i in range(2):
    if i == 0:
        url = 'https://s1.tuchong.com/content-image/201909/98cac03c4a131754ce46d51faf597230.jpg'
    else:
        break
    # 守護線程
    m = threading.Thread(target=mainFunc, args=(downpic, url))
    m.setDaemon(True)
    m.start()
    m.join()
    print(m.is_alive())
    time.sleep(100)  # sleep 100秒, 模擬for一直不結束

結果:
0.00%
1.51%
3.02%
4.52%
False
6.03%
7.54%
9.05%
10.55%

  從結果可以看出, 5秒后deamon線程結束, 意味着 t 線程會被關閉,然而子線程 t 卻一直在執行。

  怎么辦呢?

 

四、問題解決, 強制關閉子線程 

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# __author__:kzg

import threading
import time
import inspect
import ctypes
from urllib.request import urlretrieve

def callbackinfo(down, block, size):
    '''
    回調函數:
    down:已經下載的數據塊
    block:數據塊的大小
    size:遠程文件的大小
    '''
    per = 100.0 * (down * block) / size
    if per > 100:
        per = 100
    time.sleep(1)
    print('%.2f%%' % per)

# 圖片下載函數
def downpic(url):
    urlretrieve(url, 'test.jpg', callbackinfo)

def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)

for i in range(2):
    if i == 0:
        url = 'https://s1.tuchong.com/content-image/201909/98cac03c4a131754ce46d51faf597230.jpg'
    else:
        break
    t = threading.Thread(target=downpic, args=(url,))
    t.start()
    t.join(5)
    print(t.is_alive())
    if t.is_alive():
        stop_thread(t)
    print("t is kill")
    time.sleep(100)

結果:
0.00%
1.51%
3.02%
4.52%
True
t is kill

  可以看到:

    1、 主函數mainfunc去掉了

    2、在for循環中直接加入子線程

    3、在timeout的時間后線程仍然活着則強制關閉

 

 

 

附: 測試圖片下載的另一種方法

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import requests
import os
import time

def downpic(url):
    '''
    根據url下載圖片
    :param url: url地址
    :return: 下載后的圖片名稱
    '''
    try:
        print("Start Down %s" % url)
        ret = requests.get(url, timeout=3)  # 請求超時
        if ret.status_code == 200:
            with open("test.jpg", 'wb') as fp:
                for d in ret.iter_content(chunk_size=10240):
                    time.sleep(1)  # 每次下載10k,sleep 1秒
                    fp.write(d)
            print("downLoad ok %s" % url)
    except Exception as ex:
        print("downLoad pic fail %s" % url)

 

其它:

urlretrieve第三個參數為reporthook:
    是一個回調函數,當連接上服務器以及相應數據塊傳輸完畢時會觸發該回調,我們就可以利用該回調函數來顯示當前的下載進度。
    下載狀態的報告,他有多個參數,
    1)參數1:當前傳輸的塊數
    2)參數2:塊的大小
    3)參數3,總數據大小
def urlretrieve(url, filename=None, reporthook=None, data=None):
    """
    Retrieve a URL into a temporary location on disk.

    Requires a URL argument. If a filename is passed, it is used as
    the temporary file location. The reporthook argument should be
    a callable that accepts a block number, a read size, and the
    total file size of the URL target. The data argument should be
    valid URL encoded data.

    If a filename is passed and the URL points to a local resource,
    the result is a copy from local file to new file.

    Returns a tuple containing the path to the newly created
    data file as well as the resulting HTTPMessage object.
    """

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM