一个例子解释多线程

这个是CSDN一个课程的学习笔记,名称为150讲轻松搞定Python网络爬虫。

创建一个多线程

使用threading模块下的Thread类可创建一个线程。其有一个target参数,需要指定一个函数,以后这个线程执行的时候,就会执行这个函数的代码。

首先通过下面的代码比较一下单线程和多线程。

import time
import threading

def thread1():
        for x in range(3):
            print("线程1-%d"%x)
            time.sleep(1)

def thread2():
    for x in range(3):
        print("线程2-%d"%x)
        time.sleep(1)

def single_thread():
    thread1()
    thread2()

def multi_thread():
    th1 = threading.Thread(target=thread1)
    th2 = threading.Thread(target=thread2)
    th1.start()
    th2.start()

if __name__ == '__main__':
    single_thread()
    multi_thread()

执行程序可以看出,定义了两个线程之后,这两个线程几乎同时运行,可以节省一半的时间。

查看线程

  1. threading.current_thread:返回当前线程的对象。
  2. threading.enumerate:获取整个程序中所有的线程。
import time
import threading

def thread1():
    the_thread = threading.current_thread()
    print(the_thread.name)
    for x in range(3):
        # print("线程1-%d"%x)
        time.sleep(1)

def thread2():
    the_thread = threading.current_thread()
    print(the_thread.name)
    for x in range(3):
        # print("线程2-%d"%x)
        time.sleep(1)

def multi_thread():
    th1 = threading.Thread(target=thread1,name='线程1')
    th2 = threading.Thread(target=thread2,name='线程2')
    th1.start()
    th2.start()

    print("="*30)
    print(threading.enumerate())

if __name__ == '__main__':
    multi_thread()

执行结果:

在这里插入图片描述

继承自threading.Thread类

使用类可以更加方便的管理我们的代码,可以让我们使用面向对象的方式进行编程。

  1. 我们自己定义的类必须继承自threading.Thread类。
  2. 线程代码需要放在run方法中执行。
import time
import threading


class thread1(threading.Thread):
    def run(self):
        the_thread = threading.current_thread()
        for x in range(3):
            print("线程1-%d"%x)
            time.sleep(1)

class thread2(threading.Thread):
    def run(self):
        the_thread = threading.current_thread()
        for x in range(3):
            print("线程2-%d"%x)
            time.sleep(1)

def multi_thread():
    th1 = thread1()
    th2 = thread2()
    th1.start()
    th2.start()

if __name__ == '__main__':
    multi_thread()

多线程共享全局变量问题

看下面的代码:

import threading

value = 0

def add_value():
    global value
    for x in range(100):
        value += 1
    print('value的值为:',value)

def thread():
    for i in range(2):
        th = threading.Thread(target=add_value)
        th.start()

if __name__ == '__main__':
    thread()

# 运行结果
# value的值为: 100
# value的值为: 200

我们设置100次循环,貌似运行结果没有什么异常。那么将循环改成1000000次呢?

import threading

value = 0

def add_value():
    global value
    for x in range(1000000):
        value += 1
    print('value的值为:',value)

def thread():
    for i in range(2):
        th = threading.Thread(target=add_value)
        th.start()

if __name__ == '__main__':
    thread()
    
# 运行结果
# value的值为: 1428494
# value的值为: 1748155

由于多线程之间是同时进行的,第一个值超过1000000很容易理解,但是为什么第二个值达不到2000000呢?原来当两个线程同时达到加1操作时,它们并不会互相“谦让”,让对方先行,而是同时对同一个value加1,这样就相当于“浪费”了一次加一的机会,导致最终结果偏小。那么有什么方法能解决这个问题呢

threading.Lock类提供了解决办法,这个类可以在某个线程访问某个变量时加锁,防止此时其他线程再进来,直至当前线程处理结束后,才释放锁,其他线程才能进来处理。

在多线程中,如果需要修改全局变量,那么需要在修改全局变量的地方使用锁锁起来,执行完成后再把锁释放掉。

使用Lock的原则:

  1. 把尽量少的和不耗时的代码放到锁中执行。
  2. 代码执行完成后要记得释放锁。(非常重要)
    在Python中,可以使用threading.Lock来创建锁,lock.acquire()是上锁操作,lock.release()是释放锁的操作。
import threading

value = 0
gLock = threading.Lock()

def add_value():
    global value
    gLock.acquire()
    for x in range(1000000):
        value += 1
    gLock.release()
    print('value的值为:',value)

def thread():
    for i in range(2):
        th = threading.Thread(target=add_value)
        th.start()

if __name__ == '__main__':
    thread()

# 运行结果
# value的值为: 10000000
# value的值为: 20000000

生产者和消费者模式

生产者和消费者模式是多线程开发中经常见到的一种模式。生产者的线程专门用来生产一些数 据,然后存放到一个中间的变量中。消费者再从这个中间的变量中取出数据进行消费。通过生产 者和消费者模式,可以让代码达到高内聚低耦合的目标,程序分工更加明确,线程更加方便管理。

一个例子解释多线程_第1张图片

LOCK版的生产者和消费者模式
import threading
import random
import time

gMoney = 0
gLock = threading.Lock()
gTimes = 0


class Producer(threading.Thread):
    def run(self):
        global gMoney
        global gTimes
        while True:
            gLock.acquire()
            if gTimes >= 10:
                gLock.release()
                break
            money = random.randint(0, 100)
            gMoney += money
            gTimes += 1
            print("%s生产了%d元钱"%(threading.current_thread().name,money))
            time.sleep(1)
            gLock.release()


class Consumer(threading.Thread):
    def run(self) -> None:
        global gMoney
        while True:
            gLock.acquire()
            money = random.randint(0,100)
            if gMoney >= money:
                gMoney -= money
                print("%s消费了%d元钱"%(threading.current_thread().name,money))
            else:
                if gTimes >= 10:
                    gLock.release()
                    break
                print("%s想消费%d元钱,但是余额只有%d"%(threading.current_thread().name,money,gMoney))
            time.sleep(1)
            gLock.release()

def main():
    for x in range(5):
        th = Producer(name="生产者%d号"%x)
        th.start()

    for x in range(5):
        th = Consumer(name="消费者%d号"%x)
        th.start()

if __name__ == '__main__':
    main()

# 运行结果
# 生产者0号生产了80元钱
# 生产者1号生产了45元钱
# 生产者1号生产了40元钱
# 生产者1号生产了10元钱
# 生产者1号生产了61元钱
# 生产者1号生产了93元钱
# 生产者1号生产了66元钱
# 生产者1号生产了44元钱
# 生产者1号生产了8元钱
# 生产者1号生产了75元钱
# 消费者0号消费了8元钱
# 消费者0号消费了81元钱
# 消费者2号消费了96元钱
# 消费者2号消费了27元钱
# 消费者2号消费了19元钱
# 消费者2号消费了30元钱
# 消费者2号消费了61元钱
# 消费者2号消费了81元钱
# 消费者2号消费了62元钱
# 消费者2号消费了45元钱
# 消费者0号消费了3元钱
Condition版本的生产者和消费者模式:

Lock版本的生产者与消费者模式可以正常的运行。但是存在一个不足,在消费者中,总是通过while True死循环并且上锁的方式去判断钱够不够。上锁是一个很耗费CPU资源的行为。因此这种方式不是最好的。还有一种更好的方式便是使用threading.Condition来实现。threading.Condition可以在没有数据的时候处于阻塞等待状态。一旦有合适的数据了,还可以使用notify相关的函数来通知其他处于等待状态的线程。这样就可以不用做一些无用的上锁和解锁的操作。可以提高程序的性能。首先对threading.Condition相关的函数做个介绍,threading.Condition类似threading.Lock,可以在修改全局数据的时候进行上锁,也可以在修改完毕后进行解锁。以下将一些常用的函数做个简单的介绍:

  1. acquire:上锁。
  2. release:解锁。
  3. wait:将当前线程处于等待状态,并且会释放锁。可以被其他线程使用notify和notify_all函数唤醒。被唤醒后会继续等待上锁,上锁后继续执行下面的代码。
  4. notify:通知某个正在等待的线程,默认是第1个等待的线程。
  5. notify_all:通知所有正在等待的线程。notify和notify_all不会释放锁。并且需要在release之前调用。
import threading
import random
import time

gMoney = 0
gCondition = threading.Condition()
gTimes = 0


class Producer(threading.Thread):
    def run(self):
        global gMoney
        global gTimes
        while True:
            gCondition.acquire()
            if gTimes >= 10:
                gCondition.release()
                break
            money = random.randint(0, 100)
            gMoney += money
            gTimes += 1
            print("%s生产了%d元钱,剩余%d元钱"%(threading.current_thread().name,money,gMoney))
            gCondition.notify_all()
            gCondition.release()
            time.sleep(1)


class Consumer(threading.Thread):
    def run(self) -> None:
        global gMoney
        while True:
            gCondition.acquire()
            money = random.randint(0,100)
            while gMoney < money:
                if gTimes >= 10:
                    print("%s想消费%d元钱,但是余额只有%d元钱了,并且生产者已经不再生产了!"%(threading.current_thread().name,money,gMoney))
                    gCondition.release()
                    return
                print("%s想消费%d元钱,但是余额只有%d元钱了,消费失败!"%(threading.current_thread().name,money,gMoney))
                gCondition.wait()
            gMoney -= money
            print("%s消费了%d元钱,剩余%d元钱"%(threading.current_thread().name,money,gMoney))
            gCondition.release()
            time.sleep(1)


def main():
    for x in range(5):
        th = Producer(name="生产者%d号"%x)
        th.start()

    for x in range(5):
        th = Consumer(name="消费者%d号"%x)
        th.start()

if __name__ == '__main__':
    main()

# 运行结果
# 生产者0号生产了92元钱,剩余92元钱
# 生产者1号生产了97元钱,剩余189元钱
# 生产者2号生产了38元钱,剩余227元钱
# 生产者3号生产了18元钱,剩余245元钱
# 生产者4号生产了94元钱,剩余339元钱
# 消费者0号消费了18元钱,剩余321元钱
# 消费者1号消费了9元钱,剩余312元钱
# 消费者2号消费了58元钱,剩余254元钱
# 消费者3号消费了83元钱,剩余171元钱
# 消费者4号消费了41元钱,剩余130元钱
# 生产者2号生产了49元钱,剩余179元钱
# 生产者1号生产了6元钱,剩余185元钱
# 生产者3号生产了22元钱,剩余207元钱
# 生产者0号生产了99元钱,剩余306元钱
# 消费者1号消费了22元钱,剩余284元钱
# 生产者4号生产了96元钱,剩余380元钱
# 消费者2号消费了86元钱,剩余294元钱
# 消费者0号消费了8元钱,剩余286元钱
# 消费者3号消费了30元钱,剩余256元钱
# 消费者4号消费了33元钱,剩余223元钱
# 消费者2号消费了91元钱,剩余132元钱
# 消费者0号消费了72元钱,剩余60元钱
# 消费者1号想消费68元钱,但是余额只有60元钱了,并且生产者已经不再生产了!
# 消费者3号想消费93元钱,但是余额只有60元钱了,并且生产者已经不再生产了!
# 消费者4号想消费71元钱,但是余额只有60元钱了,并且生产者已经不再生产了!
# 消费者2号消费了31元钱,剩余29元钱
# 消费者0号消费了18元钱,剩余11元钱
# 消费者0号想消费19元钱,但是余额只有11元钱了,并且生产者已经不再生产了!
# 消费者2号想消费91元钱,但是余额只有11元钱了,并且生产者已经不再生产了!

线程安全的队列Queue:

在线程中,访问一些全局变量,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,那么Python内置了一个线程安全的模块叫做queue模块。Python中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后入先出)队列LifoQueue。这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么都做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。相关的函数如下:
初始化Queue(maxsize):创建一个先进先出的队列。

  1. qsize():返回队列的大小。
  2. empty():判断队列是否为空。
  3. full():判断队列是否满了。
  4. get():从队列中取最后一个数据。默认情况下是阻塞的,也就是说如果队列已经空了,那么再调用就会一直阻塞,直到有新的数据添加进来。也可以使用block=False,来关掉阻塞。如果关掉了阻塞,在队列为空的情况获取就会抛出异常。
  5. put():将一个数据放到队列中。跟get一样,在队列满了的时候也会一直阻塞,并且也可以通过block=False来关掉阻塞,同样也会抛出异常。
from queue import Queue

q = Queue(4)

for i in range(4):
    q.put(i)

print(q.qsize())

print('完成')

# 运行结果
# 4
# 完成
from queue import Queue

q = Queue(4)

for i in range(5):
    q.put(i)

print(q.qsize())

print('完成')

# 如果将range(4)改成range(5),由于q中最多只能存放4个元素,所以会被阻塞在循环内,不会执行下面的代码
from queue import Queue

q = Queue(4)

for i in range(5):
    q.put(i,block=False)   # block=False代表非阻塞,阻塞时会报错

print(q.qsize())

print('完成')

# 运行结果
'''
Traceback (most recent call last):
  File "c:/Users/woniu/Desktop/python/CSDN学院/爬虫/.vscode/03.py", line 6, in 
    q.put(i,block=False)   # block=False代表非阻塞,阻塞时会报错
  File "D:\ANACANDA\lib\queue.py", line 136, in put
    raise Full
queue.Full
'''
from queue import Queue

q = Queue(4)

for i in range(5):
    try:
        q.put(i,block=False)   # 可以利用这个报错来进行异常处理
    except:
        break

print(q.qsize())

print('完成')

# 运行结果
# 4
# 完成
from queue import Queue

q = Queue(4)

for i in range(5):
    try:
        q.put(i,block=False)  
    except:
        break

# print(q.qsize())

for j in range(4):
    value = q.get()        # get获取数据
    print(value)

print('完成')

# 运行结果
# 0
# 1
# 2
# 3
# 完成
from queue import Queue

q = Queue(4)

for i in range(5):
    try:
        q.put(i,block=False)   # 可以利用这个报错来进行异常处理
    except:
        break

# print(q.qsize())

for j in range(5):
    try:
    # get和put类似,在queue为空时也会阻塞,可以利用block=False报错,进行异常处理
        value = q.get(block=False) 
    except:
        break
    print(value)

print('完成')

# 运行结果
# 0
# 1
# 2
# 3
# 完成
from queue import Queue

q = Queue(4)

for i in range(5):
    try:
        q.put(i,block=False)   
    except:
        break

print(q.full())# 判断队列满

for j in range(5):
    try:
        value = q.get(block=False) 
    except:
        break
    print(value)

print(q.empty())# 判断队列空

print('完成')

# True
# 运行结果
# 0
# 1
# 2
# 3
# True
# 完成

你可能感兴趣的:(一个例子解释python,python,多线程,队列)