几分钟python多线程深入解读
多线程
多线程是什么,其实就相当于火影分身术,相当于五维空间的你有多个镜像分身可以同时做一件事,也相当于泡澡的同时喝咖啡。前面的话我是写了一篇博客;
几分钟搞定python多线程但是看标题也知道写的其实不全面,还有很多东西是要补充的。所以这里要做一个完整的总结,一方面方便自己另方面也方便他人。
使用多线程(threading)
要在python中使用多线程这个就可以使用模块threading。
这里就不再使用先前的小明小红跑步的例子了,因为那个例子还是比较抽象,不好理解。
首先导入threading
步骤;
1创建线程ming=threading.Thread(target=目标函数,args=[值])
2声明线程ming.setDaemon(True)
3运行线程ming.start()
4等待线程ming.join()
这里我们假设小红,小明要拿快递。
拿快递有两种方式,1,一个人去拿,2,两个人一起去拿。
显然两个人一起去拿的方式要好很多。
现在我们来模拟一下代码;(现在我们只用步骤1,2)看看会怎么样。
import threading
import time
time0=time.time()
def print_ming():
time.sleep(4)
print('我拿到了(小明)')
def print_hong():
time.sleep(3)
print('我拿到了(小红)')
ming=threading.Thread(target=print_ming)
hong=threading.Thread(target=print_hong)
ming.start()
hong.start()
print('执行完了')
time1=time.time()
print(time1-time0)
我们发现主程序先运行完了之后小明小红才运行完。那么这里我们引入快递小哥这个角色,主进程就是快递小哥,小明小红分别是两个子进程。如果我们的一个代码 里面具有功能的只是这两个或者多个子进程这个当然无所谓,但是如果是主进程也很重要也有一些功能要实现的话就很麻烦了。叫好比拿快递一样,小哥开着快递车比较快,所以我们得让他(她)等等小明,小红。
所以如果小哥也跟着跑起来小明小红是拿不到快递的,所以我们得等一的(让小哥,也就是主线程)
使用详细的步骤;
import threading
import time
time0=time.time()
def print_ming():
time.sleep(4)
print('我拿到了(小明)')
def print_hong():
time.sleep(3)
print('我拿到了(小红)')
ming=threading.Thread(target=print_ming)
hong=threading.Thread(target=print_hong)
ming.setDaemon(True)
ming.start()
hong.setDaemon(True)
hong.start()
ming.join()#让小哥即等等小明也等等小红
hong.join()
print('执行完了')
time1=time.time()
print(time1-time0)
效果如下;
上锁堵塞问题(lock)(工厂问题(我的比喻))
先不说别的先来说一说我的需求,现在我希望我的程序可以实现1000次从0 开始相加1得到1000,比如下面代码
a = 0
def go():
global a
for i in range(1000):
a+=1
print(a)
go()
现在我想让它同时执行两次也就是得到2000
代码如下;
import threading
import time
time0=time.time()
a = 0
def go():
global a
time.sleep(1)#让程序睡眠一秒便于对比
for i in range(1000):
a+=1
print(a)
def main():
tx = threading.Thread(target=go)
tx.setDaemon(True)
tx.start()
t1 = threading.Thread(target=go)
t1.setDaemon(True)
t1.start()
t1.join()
tx.join()
main()
time1=time.time()
print(time1-time0)
结果如下;
好像我达到目标 了,那么现在我们改一下将数值1000改成1000000
消费者与生产者的关系(不理解没关系记住我给的例子就可以了,毕竟敲代码又不是拽专业名词)
这个时候我们发现不太对 了,按道理得到的应该是两倍,而现在得到的是不知道什么东西。
那么现在做一个假设,现在有甲乙两个员工在工厂里生产零件,他们需要将零件放置一个到机器里面而机器只有一个放置的位置,当工人将零件放置进放置口时机器就会打包出一个包裹,打包好之后会得到一个生产票据证明工人将零件生产好了并放在机器里打包了。但是这个机器有个问题当两个零件放在一起时只会打包出一个包裹。
现在的情形与之类似,线程的执行时间时随机的,就可能发生两个线程同时使用一个变量的情况而其结果时一样的。就比如工厂的例子中甲乙两个人要加工200个零件每人100个,当老板检查时发现只有150个包裹出来,老板以为有人偷懒,然而甲乙却有200个票据证明一样。甲乙工作时将零件同时放在了机器里只打包了一个包裹。
所以为例避免这个情况这个机器要加一个锁,当里面有一个零件是就锁起来,让另一个零件等着。
使用‘锁’
threading.Lock()
在方框里面代码改一下就好了。
这里要注意一下的是假设甲乙两个工人对应一个机器1号机,那么乙丙对应2号机。所以一个锁即一个机器对应一个全局变量。也就是说此时变量为a锁是lock
但是当变量为b时需要再设置一个锁,比如lock2。换句话说,一个机器对应两个工人,另外两个工人对应另一个机器那么对应的锁也是不一样的。
上锁堵塞问题(condition)(工厂问题(我的比喻))
消费者与生产者的关系(不理解没关系记住我给的例子就可以了,毕竟敲代码又不是拽专业名词)
condition 继承了lock
但是为什么要这样使用condition呢,主要是上锁解锁比较耗费资源,有时候有些程序没有必要继续执行就没有必要去持续带锁解锁,可以换个方式让它等一等。
继续以工厂的例子解释,还是这个机器的问题,现在这个机器动不动就会过热所以这个时候就不能放零件,(不过好在这个机器有警报器可以知道有没有毛病)所以此时得让工人等一等,零件可以先拿着等一等之后机器没毛病了再放。
现在介绍它的方法(threading.Condition())
acquire() 上锁
release() 开锁
wait() 等着
notify() 警报器(只告诉离他近的工人)
notify_all() 警报器(告诉所有工人)
现在改一下代码;也改一下功能。
import threading
import time
time0=time.time()
lock=threading.Condition()
a = 0
def go():
global a
time.sleep(1)#让程序睡眠一秒便于对比
lock.acquire()#上锁
for i in range(100):
#执行a加到100
a+=1
lock.notify_all()#发警报
lock.release()#开锁
print(a)
print(threading.current_thread())#查看当前线程
def stop():
global a
lock.acquire()
for i in range(100):
while a<=-20:
lock.wait()
a-=1
#a减1减100次如果减了一百次中有减到-20(线程是随机的不一定会减到-20)
# 那么就等等,所以如果减到了-20那么最后得到0,或者80(没有到-20),或者0
#有20次减到了-20其中加了20次还要加减80次。
lock.release()
print(a)
print(threading.current_thread())#查看当前线程
def main():
tx = threading.Thread(target=go)
tx.setDaemon(True)
tx.start()
t1 = threading.Thread(target=stop)
t1.setDaemon(True)
t1.start()
t1.join()
tx.join()
main()
time1=time.time()
print(time1-time0)
结果如下;
Queue 队列
主要是为了一些变量的安全问题以及上锁的问题,当我们的队列元素不够或者满了它就会自动上锁,就像我们前面提到的那个全局变量一样,只不过这个变量里面可以写入多个值相当于一个全局列表变量。但这个家伙可以控制我们的线程暂停继续,同时当几个子线程同时调用一个全局变量时就会出现改变量不稳定的情况,比如一下被子进程1赋值为2,一下被子进程2赋值为3.那么此时该变量的值到底是2还是3。由于线程是随机的不清楚它的状态,扯个犊子就是量子态。
所以我们使用这玩意来保证这个队列这个变量的稳定,你每次修改一下我就记下来,然后给另外一个人并且这个人有选择第几次改变的值的权力。这个在多线程爬虫里面较为常用(阅读量超500就写多线程爬虫示例呦,或者等我有时间再写,反正最近是没有时间。)
1.导入这玩意是python内置的from queue import Queue
2.这玩意是一个队列式(元素先到先走)的,与之相应的还要LifoQueue(元素先到后走)栈式的。
3.基本用法
q=Queue(4) 设置队列(最大允许几个值)
q.qsize() 查看最大允许几个
q.full() 看一看有没有满
q.empty()看看有没有空,空True,满False
q.put 加元素
q.get() 得到最先加入的元素并删去
示例;
from queue import Queue
q=Queue(4)
for i in range(1,5):
q.put(i)
for i in range(4):
print(q.get())
print(q.empty())
好了基本上就这些。
本文地址:https://blog.csdn.net/FUTEROX/article/details/107471476