一. 课程安排

  • 课程内容
    • 线程间的资源竞争
    • 互斥锁和死锁
    • Queue线程
    • 线程同步

二. 课堂笔记

1. 线程间的资源竞争

一个线程写入,一个线程读取,没问题,如果两个线程都写入呢?

2. 互斥锁和死锁

2.1 互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

某个线程要更改共享数据时,先将其锁定,此时资源的状态为”锁定”,其他线程不能改变,只到该线程释放资源,将资源的状态变成”非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

  1. 创建锁
  2. mutex = threading.Lock()
  3. 锁定
  4. mutex.acquire()
  5. 解锁
  6. mutex.release()

2.2 死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

  1. import threading
  2. import time
  3. class MyThread1(threading.Thread):
  4. def run(self):
  5. # 对mutexA上锁
  6. mutexA.acquire()
  7. # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
  8. print(self.name+'----do1---up----')
  9. time.sleep(1)
  10. # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
  11. mutexB.acquire()
  12. print(self.name+'----do1---down----')
  13. mutexB.release()
  14. # 对mutexA解锁
  15. mutexA.release()
  16. class MyThread2(threading.Thread):
  17. def run(self):
  18. # 对mutexB上锁
  19. mutexB.acquire()
  20. # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
  21. print(self.name+'----do2---up----')
  22. time.sleep(1)
  23. # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
  24. mutexA.acquire()
  25. print(self.name+'----do2---down----')
  26. mutexA.release()
  27. # 对mutexB解锁
  28. mutexB.release()
  29. mutexA = threading.Lock()
  30. mutexB = threading.Lock()
  31. if __name__ == '__main__':
  32. t1 = MyThread1()
  33. t2 = MyThread2()
  34. t1.start()
  35. t2.start()

2.3 避免死锁

  • 程序设计时要尽量避免
  • 添加超时时间等

3. Queue线程

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

  1. 初始化Queue(maxsize):创建一个先进先出的队列。
  2. empty():判断队列是否为空。
  3. full():判断队列是否满了。
  4. get():从队列中取最后一个数据。
  5. put():将一个数据放到队列中。

4. 线程同步

天猫精灵:小爱同学

小爱同学:在

天猫精灵:现在几点了?

小爱同学:你猜猜现在几点了

5. 生产者和消费者

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

5.1 Lock版的生产者和消费者

  1. import threading
  2. import random
  3. gMoney = 0
  4. # 定义一个变量 保存生产的次数 默认是0次
  5. gTimes = 0
  6. # 定义一把锁
  7. gLock = threading.Lock()
  8. # 定义生产者
  9. class Producer(threading.Thread):
  10. def run(self):
  11. global gMoney
  12. global gTimes
  13. gLock.acquire() # 上锁
  14. while True:
  15. # gLock.acquire() # 上锁
  16. if gTimes >= 10:
  17. # gLock.release()
  18. break
  19. money = random.randint(0,100)
  20. gMoney += money
  21. gTimes += 1
  22. print("%s生产了%d元钱" % (threading.current_thread().name, money))
  23. gLock.release() # 解锁
  24. # 定义消费者
  25. class Consumer(threading.Thread)
  26. def run(self):
  27. global gMoney
  28. while True:
  29. gLock.acquire() # 上锁
  30. money = random.randint(0, 100)
  31. if gMoney >= money:
  32. gMoney -= money
  33. print("%s消费了%d元钱" % (threading.current_thread().name, money))
  34. else:
  35. if gTimes >= 10:
  36. gLock.release()
  37. break
  38. print("%s想消费%d元钱,但是余额只有%d"%(threading.current_thread().name,money,gMoney))
  39. gLock.release() # 解锁
  40. def main():
  41. # 开启5个生产者线程
  42. for x in range(5):
  43. th = Producer(name="生产者%d号" % x)
  44. th.start()
  45. # 开启5个消费者线程
  46. for x in range(5):
  47. th = Consumer(name="消费者%d号" % x)
  48. th.start()
  49. if __name__ == '__main__':
  50. main()

5.2 Condition版的生产者和消费者

  1. import threading
  2. import random
  3. gMoney = 0
  4. # 定义一个变量 保存生产的次数 默认是0次
  5. gTimes = 0
  6. # 定义一把锁
  7. # gLock = threading.Lock()
  8. gCond = threading.Condition()
  9. # 定义生产者
  10. class Producer(threading.Thread):
  11. def run(self):
  12. global gMoney
  13. global gTimes
  14. while True:
  15. gCond.acquire() # 上锁
  16. if gTimes >= 10:
  17. gCond.release()
  18. break
  19. money = random.randint(0,100)
  20. gMoney += money
  21. gTimes += 1
  22. print("%s生产了%d元钱,剩余%d元钱" % (threading.current_thread().name, money, gMoney))
  23. gCond.notify_all()
  24. gCond.release() # 解锁
  25. # 定义消费者
  26. class Consumer(threading.Thread):
  27. def run(self):
  28. global gMoney
  29. while True:
  30. gCond.acquire() # 上锁
  31. money = random.randint(0, 100)
  32. while gMoney < money:
  33. if gTimes >= 10:
  34. gCond.release()
  35. return # 这里如果用break只能退出外层循环,所以我们直接return
  36. print("%s想消费%d元钱,但是余额只有%d元钱了,并且生产者已经不再生产了!"%(threading.current_thread().name,money,gMoney))
  37. gCond.wait()
  38. # 开始消费
  39. gMoney -= money
  40. print("%s消费了%d元钱,剩余%d元钱" % (threading.current_thread().name, money, gMoney))
  41. gCond.release()
  42. def main():
  43. # 开启5个生产者线程
  44. for x in range(5):
  45. th = Producer(name="生产者%d号" % x)
  46. th.start()
  47. # 开启5个消费者线程
  48. for x in range(5):
  49. th = Consumer(name="消费者%d号" % x)
  50. th.start()
  51. if __name__ == '__main__':
  52. main()

5.3 多线程爬虫案例