python线程中的同步问题及解决方法


Posted in Python onAugust 29, 2019

多线程开发可能遇到的问题

假设两个线程t1和t2都要对num=0进行增1运算,t1和t2都各对num修改1000000次,num的最终的结果应该为2000000。但是由于是多线程访问,有可能出现下面情况:

from threading import Thread
import time
num = 0
def test1():
  global num
  for i in range(1000000):
    num += 1
  print("--test1--num=%d" % num)
def test2():
  global num
  for i in range(1000000):
    num += 1
  print("--test2--num=%d" % num)
if __name__ == '__main__':
  Thread(target=test1).start()
  Thread(target=test2).start()
  print("num = %d" % num)
"""
num = 134116
--test1--num=1032814
--test2--num=1166243
"""

运行结果可能不一样,但是结果往往不是2000000。问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全”。

线程同步——使用互斥锁

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。

使用互斥锁实现上面的例子:

from threading import Thread, Lock
import time
num = 0
def test1():
  global num
  # 上锁
  mutex.acquire()
  for i in range(1000000):
    num += 1
  # 解锁
  mutex.release()
  print("--test1--num=%d" % num)
def test2():
  global num
  mutex.acquire()
  for i in range(1000000):
    num += 1
  mutex.release()
  print("--test2--num=%d" % num)
start_time = time.time() # 开始时间
# 创建一把互斥锁,默认没有上锁
mutex = Lock()
p1 = Thread(target=test1)
p1.start()
# time.sleep(3)  # 取消屏蔽之后 再次运行程序,结果会不一样,,,为啥呢?
p2 = Thread(target=test2)
p2.start()
p1.join()
p2.join()
end_time = time.time() # 结束时间
print("num = %d" % num)
print("运行时间:%fs" % (end_time - start_time)) # 结束时间-开始时间
"""
输出结果:
--test1--num=1000000
--test2--num=2000000
num = 2000000
运行时间:0.287206s
"""

同步的应用——多个线程有序执行

from threading import Lock, Thread
from time import sleep
class Task1(Thread):
  def run(self):
    while True:
      # 判断是否上锁成功,返回值为bool类型
      if lock1.acquire():
        print("--task1--")
        sleep(0.5)
        lock2.release()
class Task2(Thread):
  def run(self):
    while True:
      if lock2.acquire():
        print("--task2--")
        sleep(0.5)
        lock3.release()
class Task3(Thread):
  def run(self):
    while True:
      if lock3.acquire():
        print("--task3--")
        sleep(0.5)
        lock1.release()

if __name__ == '__main__':  
  # 创建一把锁
  lock1 = Lock()  
  # 创建一把锁,并且锁上
  lock2 = Lock()
  lock2.acquire()  
  # 创建一把锁,并且锁上
  lock3 = Lock()
  lock3.acquire()  
  t1 = Task1()
  t2 = Task2()
  t3 = Task3()  
  t1.start()
  t2.start()
  t3.start()
"""
--task1--
--task2--
--task3--
--task1--
--task2--
--task3--
--task1--
--task2--
...
"""

生产者与消费者模式

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么就做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。

用FIFO队列实现上述生产者与消费者问题的代码如下:

import threading
import time
from queue import Queue
class Producer(threading.Thread):
  def run(self):
    global queue
    count = 0
    while True:
      if queue.qsize() < 1000:
        for i in range(100):
          count += 1
          msg = "生成产品" + str(count)
          queue.put(msg)
          print(msg)
      time.sleep(0.5)
class Consumer(threading.Thread):
  def run(self):
    global queue
    while True:
      if queue.qsize() > 100:
        for i in range(3):
          msg = self.name + "消费了" + queue.get()
          print(msg)
      time.sleep(0.5)

ThreadLocal

在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题

import threading
"""
⼀个ThreadLocal变量虽然是全局变量,但每个线程都只能读写⾃⼰线程的独
⽴副本,互不⼲扰。
"""
# 创建全局ThreadLocal对象:
local_school = threading.local()
def process_student():
  # 获取当前线程关联的student:
  std = local_school.student
  print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
  # 绑定ThreadLocal的student:
  local_school.student = name
  process_student()
t1 = threading.Thread(target=process_thread, args=('dongGe',), name="Thread-A")
t2 = threading.Thread(target=process_thread, args=('⽼王',), name="Thread-B")
t1.start()
t2.start()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python set常用操作函数集锦
Nov 15 Python
python实现日志按天分割
Jul 22 Python
阿里云ECS服务器部署django的方法
Aug 29 Python
Python socket实现的文件下载器功能示例
Nov 15 Python
Python+Redis实现布隆过滤器
Dec 08 Python
PyTorch中反卷积的用法详解
Dec 30 Python
新年福利来一波之Python轻松集齐五福(demo)
Jan 20 Python
python GUI库图形界面开发之PyQt5动态加载QSS样式文件
Feb 25 Python
python字典key不能是可以是啥类型
Aug 04 Python
使用Python操作MySQL的小技巧
Sep 10 Python
selenium+python实现基本自动化测试的示例代码
Jan 27 Python
python对文档中元素删除,替换操作
Apr 02 Python
python实现H2O中的随机森林算法介绍及其项目实战
Aug 29 #Python
flask/django 动态查询表结构相同表名不同数据的Model实现方法
Aug 29 #Python
深入了解python中元类的相关知识
Aug 29 #Python
Django shell调试models输出的SQL语句方法
Aug 29 #Python
python实现文件的分割与合并
Aug 29 #Python
Python配置文件处理的方法教程
Aug 29 #Python
浅谈django url请求与数据库连接池的共享问题
Aug 29 #Python
You might like
使用PHP socke 向指定页面提交数据
2008/07/23 PHP
PHPThumb PHP 图片缩略图库
2012/03/11 PHP
PHP反向代理类代码
2014/08/15 PHP
使用PHP实现微信摇一摇周边红包
2016/01/04 PHP
使用php自动备份数据库表的实现方法
2017/07/28 PHP
浅谈Laravel POST,PUT,PATCH 路由的区别
2019/10/15 PHP
原创javascript小游戏实现代码
2010/08/19 Javascript
使用js+jquery实现无限极联动
2013/05/23 Javascript
z-blog SyntaxHighlighter 长代码无法换行解决办法(基于jquery)
2015/11/18 Javascript
基于Node.js实现nodemailer邮件发送
2016/01/26 Javascript
canvas绘制多边形
2017/02/24 Javascript
jQuery实现div跟随鼠标移动
2020/08/20 jQuery
jQuery开源组件BootstrapValidator使用详解
2017/06/29 jQuery
vue中如何让子组件修改父组件数据
2018/06/14 Javascript
JS调用安卓手机摄像头扫描二维码
2018/10/16 Javascript
基于element-ui封装表单金额输入框的方法示例
2021/01/06 Javascript
[03:02]2014DOTA2西雅图邀请赛 让队员自己告诉你DK NAVI备战情况
2014/07/08 DOTA
python中scikit-learn机器代码实例
2018/08/05 Python
Python爬虫实现抓取京东店铺信息及下载图片功能示例
2018/08/07 Python
Python中最大递归深度值的探讨
2019/03/05 Python
详解python实现交叉验证法与留出法
2019/07/11 Python
python3 tcp的粘包现象和解决办法解析
2019/12/09 Python
Python验证码截取识别代码实例
2020/05/16 Python
Python通过fnmatch模块实现文件名匹配
2020/09/30 Python
入党申请书自我鉴定
2013/10/12 职场文书
小学中秋节活动方案
2014/02/06 职场文书
计算机数据库专业职业生涯规划书
2014/02/08 职场文书
《猫》教学反思
2014/02/26 职场文书
供应链金融服务方案
2014/05/25 职场文书
公司总经理助理岗位职责
2014/07/09 职场文书
2015年教师节慰问信
2015/03/23 职场文书
2015年秋季小学开学典礼主持词
2015/07/16 职场文书
民政局2016年“六一”儿童节慰问活动总结
2016/04/06 职场文书
血轮眼轮回眼特效 html+css
2021/03/31 HTML / CSS
PHP控制循环操作的时间
2021/04/01 PHP
总结python多进程multiprocessing的相关知识
2021/06/29 Python