【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

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值