一、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)
要注意的是:
- 计时是对主线程计时,主线程结束,计时随之结束,打印出主线程的用时。
- 主线程的任务完成之后,主线程随之结束,子线程继续执行自己的任务,直到全部的子线程的任务全部结束,程序结束。
>> 第二点:将子线程设置成守护线程的情况
当我们使用 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参数
- 当设置守护线程时,含义是主线程对于子线程等待 timeout 的时间后将会杀死该子线程,最后退出程序。所以说,如果有10个子线程,全部的等待时间就是每个 timeout 的累加和。简单的来说,就是给每个子线程一段 timeout 的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死该子线程。
- 没有设置守护线程时,只会对主线程的退出时间产生影响。主线程将会等待 timeout 的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。
- 因此如果给 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