聊一下Python中的多线程

412 阅读4分钟

图怪兽_058cb39a012edabc8d99b6fa5813e6bc_58776.jpg

背景

多线程在每个计算机语言中都扮演着非常重要的角色。那么多线程我们又该如何理解?比如在现实生活中我们可以一边做饭一边追剧,一边洗澡一边哼着小曲。也就是简单的事情,我们自然可以三心二意,这个是我们大脑的出厂设定就有的东西。

但是复杂的精密作业,我们可能还是要一样一样来,不能同时进行,不然你试试在别人在背课文的时候你给人家讲个笑话。

至于计算机中的多线程,道理也是一样。因为计算机运算速度很快,而且采用的时间片技术和各种调度算法,可以保证每个程序都能得到执行,延时非常短,我们是感觉不出来的。比如几个程序在一秒钟之内就轮换执行了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()

运行效果

image.png

通过运行的效果,我们可以直接看出当线程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()

运行结果

image.png

通过执行结果显而易见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()

执行结果

image.png

我们发现结果是有问题的,产生问题的原因是什么呢?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()

运行效果

image.png

这样我们就完美的解决了线程间资源竞争的问题。当然多线程的技术博大精深,本文可以让同学们快速入门Python多线程.关于后续的内容可以持续关注文章的更新!