前一篇介绍了python多线程相关的知识,这一节将进行细讲threading库。
前文学习:
[python渗透测试自学篇]一、TCP扫描器之多线程:threading模块(1)
编辑不易,转载请联系说明用途,并标记作者姓名和文章来源!
首先导入threading模块
import threading
其中threading模块最核心的内容是Thread(一定要大写)这个类。
我们要创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。
import threading
import time
def test(i):
print(i);
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=test,args=(i,));
t.start();
----------------------
02134
每次结果都不一样
threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None);
一般情况下只是用target,如果需要传参则使用args,格式为args=(x,)
对象 | 描述 |
---|---|
start() | 开启线程活动 ,方法一个线程只能被调用一次 |
run() | 此方法代表线程活动 |
join(timeout=none) | 让当前调用者线程等待,直到线程结束 ,timeout参数是以秒为单位的浮点数 |
name | 线程的名称字符串 |
ident | 线程的标识符,如果线程还没有启动,则为None,ident是一个非零整数 |
is_alive() | 线程是否存活,返回True或者False。 |
daemon | 表示该线程是否是守护线程,True或者False |
Barrier | 创建一个障碍,必须达到指定数量线程才开始运行 |
from threading import Thread
import time
def a(name):
print('%s 开启线程' % name);
print('%s 关闭线程' % name);
if __name__ == "__main__":
obj = Thread(target=a, args=('啦啦啦',));
obj.start();
print('主进程/主线程');
----------------------
主进程/主线程啦啦啦 开启线程
啦啦啦 关闭线程
from threading import Thread
import time
class MyThread(Thread): # 继承父类threading.Thread方法
def __init__(self, name):
super().__init__()
self.name = name;
def run(self):
print('%s 线程开启 ' % self.name);
print('%s 线程结束 ' % self.name);
if __name__ == "__main__":
obj = MyThread('啦啦啦');
obj.start();
print('主进程/主线程');
print('主进程/主线程');
----------------------
啦啦啦 线程开启 主进程/主线程
啦啦啦 线程结束 主进程/主线程
以上两种都是多线程,推荐使用第一种,直接调用Thread这个类
函数名 | 描述 |
---|---|
threading.active_count() | 开启线程活动 ,方法一个线程只能被调用一次 |
threading.current_thread() | 此方法代表线程活动 |
threading.get_ident() | 让当前调用者线程等待,直到线程结束 ,timeout参数是以秒为单位的浮点数 |
threading.enumerate() | 线程的名称字符串 |
threading.main_thread() | 线程的标识符,如果线程还没有启动,则为None,ident是一个非零整数 |
threading.stack_size([size]) | 线程是否存活,返回True或者False。 |
from threading import Thread
import threading
def work():
import time
time.sleep(3)
print('子线程对象:', threading.current_thread())
if __name__ == '__main__':
# 在主进程下开启线程
t = Thread(target=work)
t.start()
print('调用线程对象:', threading.current_thread()) # 返回此函数的调用者控制的threading.Thread线程对象
print('调用线程对象名字: ',threading.current_thread().getName()) # 获取主线程名字
print('运行线程:', threading.enumerate()) # 返回当前活着的Thread对象的列表。
print('存活线程数量:', threading.active_count()) # 返回当前存活的Thread线程对象数量
print('主线程对象:', threading.main_thread()) # 返回主线程对象
print('主线程/主进程')
----------------------
调用线程对象: <_MainThread(MainThread, started 8096)>
调用线程对象名字: MainThread
运行线程: [<_MainThread(MainThread, started 8096)>, <Thread(SockThread, started daemon 14908)>, <Thread(Thread-1, started 1588)>]
存活线程数量: 3
主线程对象: <_MainThread(MainThread, started 8096)>
主线程/主进程
子线程对象: <Thread(Thread-1, started 1588)>
所谓线程守护,就是主线程不管该线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。也就是说:主线程不等待该守护线程的执行完再去关闭。
import threading
import time
def run(n):
print('task',n);
time.sleep(1);
print('3s');
time.sleep(1);
print('2s');
time.sleep(1);
print('1s');
if __name__ == '__main__':
t=threading.Thread(target=run,args=('t1',));
#t.setDaemon(True); #把子线程设置为守护线程,必须在start()之前设置
t.start();
print('end');
----------------------
taskend
t1
>>>
3s
2s
1s
import threading
import time
def run(n):
print('task',n);
time.sleep(1);
print('3s');
time.sleep(1);
print('2s');
time.sleep(1);
print('1s');
if __name__ == '__main__':
t=threading.Thread(target=run,args=('t1',));
#t.setDaemon(True); #把子线程设置为守护线程,必须在start()之前设置
t.start();
print('end');
----------------------
taskend
t1
通过执行结果可以看出,设置守护线程之后,当主线程结束时,子线程也将立即结束,不再执行
一般这里会有很多人实验失败,因为在交互模式下守护线程还是会继续执行的,要在cmd中打开才可以实现守护线程加粗样式的显示。所以测试的时候不要在交互模式下跑。
原因也不太清楚,个人感觉是pythonIDEL没有很好地支持守护线程吧,或则是可以实现但显示问题。Pycharm就不太清楚了,可以自己实验一下。
为了让守护线程执行结束之后,主线程再结束,我们可以使用join方法,让主线程等待子线程执行
import threading
import time
def run(n):
print('task',n);
time.sleep(1);
print('3s');
time.sleep(1);
print('2s');
time.sleep(1);
print('1s');
if __name__ == '__main__':
t=threading.Thread(target=run,args=('t1',));
t.setDaemon(True);
t.start();
t.join();
print('end');
----------------------
task t1
3s
2s
1s
end
>>>
运行完毕,并不意味着运行终止了
主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。
主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
上一章讲解过同步和异步的区别,有人就会问了,多线程不就是异步吗,为什么还要考虑同步。
举个栗子大家就知道了。
需求:构建100个线程,每个线程对主进程定义的变量n=100 做减1 操作,得到结果应该是0
from threading import Thread
import time
def work():
global n;
temp = n;
time.sleep(0.1);
n = temp-1;
if __name__ == '__main__':
n = 100
l = []
for i in range(100):
T = Thread(target=work); # 生成100个线程
l.append(T);
T.start();
for T in l:
T.join();
print(n);
----------------------
99
线程开销小,执行速度很快,在time.sleep(0.1)等待期间,100个线程拿到的temp都是100,执行减1操作,结果99,如果想让结果变为0,则需要给线程加上Lock锁
使用互斥锁
from threading import Thread,Lock
import time
def work():
global n;
R.acquire();
temp = n;
time.sleep(0.1);
n = temp-1;
R.release();
if __name__ == '__main__':
R = Lock();
n = 100
l = []
for i in range(100):
T = Thread(target=work); # 生成100个线程
l.append(T);
T.start();
for T in l:
T.join();
print(n);
----------------------
0
可以发现使用了互斥锁后就成正常了。
说白了就是在同一时间,可以只允许设定的数量的线程去执行
import threading
import time
def run(n):
semaphore.acquire() # 加信号量锁
time.sleep(5)
print("run the thread: %s\n" % n)
semaphore.release() # 释放信号量锁
if __name__ == '__main__':
semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行(Bounded:绑定,Semaphore:信号量)
for i in range(20):
t = threading.Thread(target=run, args=(i,))
t.start()
while threading.active_count() != 1:
pass
else:
print('----all threads done---')
上面程序的执行,会让人感觉是:分了4组,前5个同时完成,然后又5个同时进去。但是实际的效果是:这5个里面如果有3个完成,就会立刻再放3个进去。不会等5个都完成,每出来1个就放进去1个,出来几个放进去几个。
指的是有两个自定义线程 :线程一 , 线程二 。
线程二执行的前提必须是线程一代码完完整整执行完毕后才执行线程二
这种事件称之为Event事件
跟红绿灯一样
from threading import Thread,Event
import time
event=Event()
def light():
print('红灯正亮着')
time.sleep(3)
event.set() #绿灯亮
def car(name):
print('车%s正在等绿灯' %name)
event.wait() #等灯绿
print('车%s通行' %name)
if __name__ == '__main__':
# 红绿灯
t1=Thread(target=light)
t1.start()
# 车
for i in range(10):
t=Thread(target=car,args=(i,))
t.start()
有两个线程,红绿灯和车
车想要过马路,就必须先让红绿灯先变成绿灯(也就是先让红绿灯运行完毕),才能过。
这一个知识点下一大点(生产者消费者模式)中讲解。
什么是生产者?
食物链中能自己制造事物的生物叫生产者
什么是消费者?
直接消费或间接消费别的生物制造的食物的生物叫做消费者
为什么引入生产者与消费者模型?
在并发编程中,如果生产者处理速度很快,而消费者处理速度比较慢,那么生产者就必须等 待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那 么消费者就必须等待生产者。为了解决这个等待的问题,就引入了生产者与消费者模型。让 它们之间可以不停的生产和消费。
那什么是生产者消费者模式?
生产者与消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据,不需要等待消费者处理,直接扔给队列,消费者不找生产者要数据,而是直接从阻塞 队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
举个例子:
在餐厅中,如果你去消费,你作为一个消费者,点菜,只需要告诉前台,告诉服务员,你不用去厨房找厨师。客户的饭菜,也不是厨师直接给你,而是服务员给你端过来的。这个服务员或者前台就相当于队列,厨师就是生产者,客户就是消费者。
from queue import Queue
from threading import Thread
import time
#创建队列
q=Queue(10)
def producer(name):
count=1 #给生产的包子计数
while True:
q.join() #等待task——done()发送信号
q.put(count)
print("%s正在生产第%d个包子"%(name,count))
count+=1
time.sleep(2)
def customer(name):
count=1
while True:
bao_zi=q.get()
print("消费者{}正在池第{}个包子".format(name,bao_zi))
count+=1
q.task_done()
time.sleep(1)
if __name__ == '__main__':
t1= Thread(target=producer,args=("张三",))
t2= Thread(target=customer,args=("李四",))
t1.start()
t2.start()
'''
-------------------
运行结果:
张三正在生产第1个包子
消费者李四正在池第1个包子
张三正在生产第2个包子消费者李四正在池第2个包子
张三正在生产第3个包子消费者李四正在池第3个包子
张三正在生产第4个包子消费者李四正在池第4个包子
张三正在生产第5个包子消费者李四正在池第5个包子
张三正在生产第6个包子消费者李四正在池第6个包子
张三正在生产第7个包子消费者李四正在池第7个包子
张三正在生产第8个包子
消费者李四正在池第8个包子
张三正在生产第9个包子
消费者李四正在池第9个包子
'''
from queue import Queue
from threading import Thread
import time
#创建队列
q=Queue(10)
def producer(name):
count=1 #给生产的包子计数
while True:
q.join() #等待task——done()发送信号
q.put(count)
print("%s正在生产第%d个包子"%(name,count))
count+=1
time.sleep(2)
def customer1(name):
count=1
while True:
bao_zi=q.get()
print("消费者1{}正在池第{}个包子".format(name,bao_zi))
count+=1
q.task_done()
time.sleep(1)
def customer2(name):
count = 1
while True:
bao_zi = q.get()
print("消费者2{}正在池第{}个包子".format(name, bao_zi))
count += 1
q.task_done()
time.sleep(1)
if __name__ == '__main__':
t1= Thread(target=producer,args=("张三",))
t2= Thread(target=customer1,args=("李四",))
t3 = Thread(target=customer2, args=("小吴",))
t1.start()
t2.start()
t3.start()
'''
-------------------
运行结果:
张三正在生产第1个包子
消费者1李四正在池第1个包子
张三正在生产第2个包子
消费者2小吴正在池第2个包子
张三正在生产第3个包子消费者1李四正在池第3个包子
张三正在生产第4个包子
消费者2小吴正在池第4个包子
张三正在生产第5个包子
消费者1李四正在池第5个包子
张三正在生产第6个包子
消费者2小吴正在池第6个包子
张三正在生产第7个包子
消费者1李四正在池第7个包子
张三正在生产第8个包子消费者2小吴正在池第8个包子
张三正在生产第9个包子
消费者1李四正在池第9个包子
张三正在生产第10个包子消费者2小吴正在池第10个包子
'''
import time
def consumer(name):
'''
这是一个生成器,调用的时候才执行
:param name:消费者的名字
:return:包子
'''
print("消费者准备吃包子-------")
while True:
new_baozi = yield #接受调用生成器的值
print("%s吃第%d个包子"%(name,new_baozi))
time.sleep(1)
def producer(name):
r = con.__next__()
r = con2.__next__()
count=1
while True:
print("%s正在生产第%d个包子和第%d个包子"%(name,count,count+1))
#调用生成器并且发送数据
con.send(count)
con2.send(count+1)
count+=2
if __name__ == '__main__':
con = consumer("张三")
con2 = consumer("李四")
p=producer("大厨")
'''
-------------------
运行结果:
消费者准备吃包子-------
消费者准备吃包子-------
大厨正在生产第1个包子和第2个包子
张三吃第1个包子
李四吃第2个包子
大厨正在生产第3个包子和第4个包子
张三吃第3个包子
李四吃第4个包子
大厨正在生产第5个包子和第6个包子
张三吃第5个包子
李四吃第6个包子
大厨正在生产第7个包子和第8个包子
张三吃第7个包子
李四吃第8个包子
大厨正在生产第9个包子和第10个包子
张三吃第9个包子
李四吃第10个包子
大厨正在生产第11个包子和第12个包子
张三吃第11个包子
'''
使用了互斥锁就会变成像单线程一样,那为什么还是使用互斥锁呢?因为单线程只有一个,但是多线程可以有多个锁,而且在python中CPU密集型任务中(类似于运算之类的(使用cpu比较多))多进程比多线程更好用。一般很少使用到互斥锁。
信号量就像是互斥锁的升级版,它可以同时容纳多个线程共同访问一个公共资源,也不适合运算,如果使用互斥锁的例子,结果只会类似于80这样的数值
用于依赖其他线程的数据来支撑直接线程的运行,跟互斥锁很像,但是数量上又有不同
这个就是队列,排队进场,排队出厂,按顺序来。