背景
多线程在每个计算机语言中都扮演着非常重要的角色。那么多线程我们又该如何理解?比如在现实生活中我们可以一边做饭一边追剧,一边洗澡一边哼着小曲。也就是简单的事情,我们自然可以三心二意,这个是我们大脑的出厂设定就有的东西。
但是复杂的精密作业,我们可能还是要一样一样来,不能同时进行,不然你试试在别人在背课文的时候你给人家讲个笑话。
至于计算机中的多线程,道理也是一样。因为计算机运算速度很快,而且采用的时间片技术和各种调度算法,可以保证每个程序都能得到执行,延时非常短,我们是感觉不出来的。比如几个程序在一秒钟之内就轮换执行了200多次,我们只会以为每一个程序都是没有被中断过的,是单独一直在执行的。
那我们来思考一个问题爬虫为什么需要多线程?最主要的一个原因就是效率。比如以前我们爬取一个数据都是同步。就比如爬取图片,我们肯定是爬取完了 一张在爬取下一张。多线程就可以同时爬取很多张。但是 大家也应该知道事物都是有正反两方面。太快了有时候也不太好。
多线程的创建
- 通过函数来创建
import time
import threading
def demo():
print('hello 子线程')
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=demo)
time.sleep(1)
t.start()
- 通过类来创建
import threading
class A(threading.Thread):
def run(self) -> None:
for i in range(5):
print(i)
if __name__ == '__main__':
a = A()
a.start()
查看线程的数量
查看线程的数量我们用到一个enumerate()方法 我们用代码来演示一下
import time
import threading
def demo1():
for i in range(5):
print('demo1--%d'%i)
time.sleep(1)
def demo2():
for i in range(10):
print('demo2--%d' % i)
time.sleep(1)
def main():
t1 = threading.Thread(target=demo1)
t2 = threading.Thread(target=demo2)
t1.start()
t2.start()
while True:
print(threading.enumerate())
if len(threading.enumerate()) <= 1:
break
time.sleep(1)
if __name__ == '__main__':
main()
运行效果
通过运行的效果,我们可以直接看出当线程1没有消亡的时候一共有3个线程分别是线程一、线程二还有主线程。当线程一消亡的时候就只剩下线程二和主线程了。最后线程二也消亡了,就只剩下一个主线程。
子线程的执行与创建
import time
import threading
# 当调用start()时,才会真正的创建线程,并且开始执行
def demo():
for i in range(5):
print('demo--%d'%i)
time.sleep(1)
def main():
print(threading.enumerate())
t1 = threading.Thread(target=demo)
print(threading.enumerate())
t1.start() # 创建并执行线程
print(threading.enumerate())
if __name__ == '__main__':
main()
运行结果
通过执行结果显而易见start()方法才是创建并执行线程
线程间的资源竞争
这个是什么情况呢?其实也比较好理解。就是当进程中的多个线程,同时读取一块内存数据,与此同时其中一个或多个线程修改了这块内存数据。这样就会导致不可预期的结果,因为线程不安全引起的错误往往非常难发现
import threading
import time
num = 0
def demo1(nums):
global num
for i in range(nums):
num += 1
print('demo1-num-%d'%num)
def demo2(nums):
global num
for i in range(nums):
num += 1
print('demo2-num-%d' % num)
def main():
t1 = threading.Thread(target=demo1,args=(1000000,))
t2 = threading.Thread(target=demo2,args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print('main-num-%d' % num)
if __name__ == '__main__':
main()
执行结果
我们发现结果是有问题的,产生问题的原因是什么呢?CPU在处理两个线程的时候采用的是雨露均沾的方式。可能在咱们的线程1中 num刚刚+1 还没有来得及赋值的时候。就开始处理线程2了。那么当线程2在刚执行 num+1的操作的时候,它又去 执行线程1未完成的操作。所以咱们会出现一种值的覆盖的情况。所以就导致我们现在打印的结果是不对的。
那么我们该如何解决呢?我们可以通过线程锁的方式来进行解决
import threading
import time
num = 0
lock = threading.Lock()
def demo1(nums):
global num
# 加锁
lock.acquire()
for i in range(nums):
num += 1
# 解锁
lock.release()
print('demo1-num-%d'%num)
def demo2(nums):
global num
lock.acquire()
for i in range(nums):
num += 1
lock.release()
print('demo2-num-%d' % num)
def main():
t1 = threading.Thread(target=demo1,args=(1000000,))
t2 = threading.Thread(target=demo2,args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print('main-num-%d' % num)
if __name__ == '__main__':
main()
运行效果
这样我们就完美的解决了线程间资源竞争的问题。当然多线程的技术博大精深,本文可以让同学们快速入门Python多线程.关于后续的内容可以持续关注文章的更新!