python多線程編程|threading模塊|多線程編程|多線程TCP端口掃描|python多線程任務

 

一、threading 模塊

多線程的好處自然不必多說,下面是簡單的對於線程的介紹,線程可以分爲:

  • 內核線程:由操作系統內核創建和撤銷。
  • 用戶線程:不需要內核支持而在用戶程序中實現的線程。

Python3 線程中常用的兩個模塊爲:

  • _thread
  • threading(推薦使用)

thread 模塊已被廢棄,在 Python3 中不能再使用"thread" 模塊,爲了兼容性,Python3 將 thread 重命名爲 "_thread",推薦用 threading 完成需求即可,threading 模塊除了包含 _thread 模塊中的所有方法外,還提供的其他方法。

 

二、多線程實現

利用 threading 模塊完成多線程的操作有兩種方式,一是直接生成Thread類對象,使用對象來調用其中的各種方法,二是用子類來繼承 Thread 類,並重寫 Thread 類中的 run 方法。

1、使用 Thread類對象實現多線程——分別計算兩個1到1000的和

#!/usr/bin/python3

import threading,time

def sum(n):
  sum = 0
  for i in range(1,n+1):
    sum += i
    time.sleep(0.001)
  print(sum)

''' 單線程 '''
print ('======= Single Thread')
time1 = time.time() #開始計時
sum(1000)
sum(1000)
interval = time.time()-time1
print('interval:',interval)
''' 多線程 '''
print('======== Multithreading')
n = [1000,1000]
mythread = []
time2 = time.time()
for i in range(len(n)):#對列表中的元素依次操作,有幾個元素就新建幾個線程對象
  newThread = threading.Thread(target = sum,args = (n[i],))
  mythread.append(newThread)    #將線程對象加入列表
for i in range(len(n)):
  mythread[i].start()
for i in range(len(n)):
  mythread[i].join() #等待線程執行結束
interval2 = time.time()-time2
print('interval2:',interval2)

 

2、使用類繼承的方式實現多線程

#!/usr/bin/python3

import threading

class mythread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    
    def run(self):
        global n #全局變量
        if lock.acquire(): # 給資源加鎖,不加鎖的話輸出就很亂,因爲10個線程共享了該設備。
            print('Thread:',n)
            n += 1
            lock.release()
''' 輸出這種I\O操作會用到設備資源,因此需要加鎖,首先新建lock鎖對象 
    加鎖的目的也就是不讓共享的資源被多個線程同時使用,避免一些不確定的結果 '''
n = 0
t = []
lock = threading.Lock() # 生成鎖對象
for i in range(10): # 生成10個線程對象
    my = mythread()
    t.append(my)
for i in range(10):
    t[i].start()
for i in range(10):
    t[i].join()

 

三、多線程 TCP 端口掃描

先用簡單的單線程來做,發現用了20秒左右的時間。

# -*- coding:utf-8 -*-

from socket import *  # 需要用到 socket編程
import sys,time

ports = [21,23,25,53,69,80,135,139,1521,1433,3306,3389]
HOST = sys.argv[1] # 接收用戶輸入的IP

print("Scanning...\n")

t1 = time.time()
for p in ports:
    try:
        tcpCliSock = socket(AF_INET,SOCK_STREAM)
        tcpCliSock.connect((HOST,p)) # 嘗試連接端口
        print(str(p) + " -> open") # 端口開放
    except error:
        print(str(p) + " -> not open")
    finally:
        tcpCliSock.close()
        del tcpCliSock  # 關閉刪除連接
print("interval:",time.time()-t1)

改成多線程執行試試,用繼承類的方法,可以看到很快得到掃描結果。這裏有個注意點請看:https://blog.csdn.net/Cody_Ren/article/details/104504400

# -*- coding:utf-8 -*-

from socket import *  # 需要用到 socket編程
import sys,time,threading

class mythread(threading.Thread):
    def __init__(self, fun, args):
        threading.Thread.__init__(self)
        self.fun = fun
        self.args = args
    def run(self):  # 注意這裏對run的重新實現方法
        self.fun(*self.args) # python2中的apply方法直接改成這樣

def scan(h,p):
    try:
        tcpCliSock = socket(AF_INET,SOCK_STREAM)
        tcpCliSock.connect((h,p)) # 嘗試連接端口
        # 輸出之前判斷一下是否得到了設置的鎖
        if lock.acquire():
            print(str(p) + " -> open") # 端口開放
            lock.release() # 得到資源用完之後釋放鎖
    except error:
        # 這裏同樣要判斷是否獲取到了針對I/O資源的鎖
        if lock.acquire():
            print(str(p) + " -> not open")
            lock.release()
    finally:
        tcpCliSock.close()
        del tcpCliSock  # 關閉刪除連接

ports = [21,23,25,53,69,80,135,139,1521,1433,3306,3389]
HOST = sys.argv[1] # 接收用戶輸入的IP

# 這裏要設置一把鎖
lock = threading.Lock()

mt = [] # 存儲多線程對象的列表
for p in ports:
    t = mythread(scan,(HOST,p))
    mt.append(t)

print("Scanning...\n")
t1 = time.time()
for m in mt:
    m.start() # 執行多線程即執行多線程列表中的每一個線程對象
for m in mt:
    m.join()    #等待多線程結束

print("interval:",time.time()-t1)

 

四、細節

1、join() 的作用,如下分爲幾點展開敘述

>> 第一點:python多線程的默認情況

當一個進程啓動之後,默認產生一個主線程,因爲線程是程序執行流的最小單元。當此後設置多線程時,主線程就會創建多個子線程。在python中,默認情況下(其實就是setDaemon(False),即默認不是守護線程),主線程執行完自己的任務以後,就退出了,此時子線程會繼續執行自己的任務,直到自己的任務結束,如下在主線程中創建5個子線程,並多線程執行。

# -*- coding:utf-8 -*-

import threading
import time

def run():
    time.sleep(2)
    print('當前線程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('我是主線程,我叫:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run) #每個線程的任務就是執行 run方法
        thread_list.append(t)

    for t in thread_list:
        t.start()

    print('主線程結束!' , threading.current_thread().name)
    print('一共用時:', time.time()-start_time)

要注意的是:

  1. 計時是對主線程計時,主線程結束,計時隨之結束,打印出主線程的用時。
  2. 主線程的任務完成之後,主線程隨之結束,子線程繼續執行自己的任務,直到全部的子線程的任務全部結束,程序結束。

>> 第二點:將子線程設置成守護線程的情況
當我們使用 setDaemon(True) 方法,設置子線程爲守護線程時,主線程一旦執行結束,則全部線程全部被終止執行。可能出現的情況就是,子線程的任務還沒有完全執行結束,就被迫停止運行,注意這裏只是把每一條子線程設置成了守護線程。注意 setDaemon() 在 start() 之前,且非常明顯的看到,主線程結束以後,子線程還沒有來得及執行,整個程序就退出了。可是這樣有什麼意義呢,繼續往下看。

import threading
import time

def run():

    time.sleep(2)
    print('當前線程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('我是主線程,我叫:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        t.setDaemon(True)
        t.start()

    print('主線程結束了!' , threading.current_thread().name)
    print('一共用時:', time.time()-start_time)

>> 第三點:join() 的作用
此時 join() 函數的作用就來了,join 所完成的工作就是線程同步,即主線程任務結束之後,進入阻塞狀態,一直等待其他所有的子線程執行結束後,主線程纔可以終止,如下所示可以看到,主線程一直等待全部的子線程結束之後,主線程自身才結束,程序退出。這也是之前編程一直針對線程列表的每一個對象執行 join方法的原因,注意是編程時最後針對每一個線程對象都要加上join方法哦。

import threading
import time

def run():

    time.sleep(2)
    print('當前線程的名字是: ', threading.current_thread().name)
    time.sleep(2)


if __name__ == '__main__':

    start_time = time.time()

    print('這是主線程:', threading.current_thread().name)
    thread_list = []
    for i in range(5):
        t = threading.Thread(target=run)
        thread_list.append(t)

    for t in thread_list:
        t.setDaemon(True)
        t.start()

    for t in thread_list:
        t.join()

    print('主線程結束了!' , threading.current_thread().name)
    print('一共用時:', time.time()-start_time)

>> 第四點:join函數有一個timeout參數

  1. 當設置守護線程時,含義是主線程對於子線程等待 timeout 的時間後將會殺死該子線程,最後退出程序。所以說,如果有10個子線程,全部的等待時間就是每個 timeout 的累加和。簡單的來說,就是給每個子線程一段 timeout 的時間,讓他去執行,時間一到,不管任務有沒有完成,直接殺死該子線程。
  2. 沒有設置守護線程時,只會對主線程的退出時間產生影響。主線程將會等待 timeout 的累加和這樣的一段時間,時間一到,主線程結束,但是並沒有殺死子線程,子線程依然可以繼續執行,直到子線程全部結束,程序退出。
  3. 因此如果給 join 函數傳入了 timeout 參數,那麼一般是配合守護線程同時存在的,參照上面的第二點守護線程。因此 1 的情況用的比較多。

2、關於設置線程爲守護線程的問題

上面第二點關於守護線程,爲了便於理解故意寫錯了,實際上主線程在結束之後,被設置爲守護線程的子線程們並沒有被殺死!!只是說對於這些守護線程而言:情況①:如果最後一個非守護線程執行完了,它們才執行完畢的話,控制檯並不會顯示這些守護線程的信息;情況②:但如果某個守護線程先於最後一個非守護線程完成的話(且完成時間晚於主線程),其完成信息仍會在控制檯顯示。由此可見,主線程完成後並沒有殺死守護線程,而是針對上面描述的第一種情況對其完成信息不再關心。(詳見《python核心編程》)

 

3、關於線程優先級,暫時沒用到,不做記錄

https://www.runoob.com/python3/python3-multithreading.html

https://blog.csdn.net/weixin_40481076/article/details/101594705

 

 

 

參考:

https://www.runoob.com/python3/python3-multithreading.html

https://www.cnblogs.com/cnkai/p/7504980.html

https://www.runoob.com/python3/python3-multithreading.html

https://blog.csdn.net/weixin_40481076/article/details/101594705

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章