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的Flask框架中实现分页功能的教程
Apr 20 Python
Windows下Python3.6安装第三方模块的方法
Nov 22 Python
几个适合python初学者的简单小程序,看完受益匪浅!(推荐)
Apr 16 Python
解决python3中的requests解析中文页面出现乱码问题
Apr 19 Python
Python使用Beautiful Soup爬取豆瓣音乐排行榜过程解析
Aug 15 Python
Python自动化导出zabbix数据并发邮件脚本
Aug 16 Python
Python产生一个数值范围内的不重复的随机数的实现方法
Aug 21 Python
opencv调整图像亮度对比度的示例代码
Sep 27 Python
Python3.7 读取音频根据文件名生成脚本的代码
Apr 07 Python
OpenCV+python实现膨胀和腐蚀的示例
Dec 21 Python
python 基于opencv操作摄像头
Dec 24 Python
Python实现邮件发送的详细设置方法(遇到问题)
Jan 18 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中的类-什么叫类
2006/11/20 PHP
如何使用PHP中的字符串函数
2006/11/24 PHP
PHP Token(令牌)设计
2008/03/15 PHP
Php注入点构造代码
2008/06/14 PHP
9段PHP实用功能的代码推荐
2014/10/14 PHP
PHP 7的一些引人注目的新特性简单介绍
2015/11/08 PHP
PHP截取IE浏览器并缩小原图的方法
2016/03/04 PHP
PHP+Ajax验证码验证用户登录
2016/07/20 PHP
PHP基于迭代实现文件夹复制、删除、查看大小等操作的方法
2017/08/11 PHP
PHP实现求连续子数组最大和问题2种解决方法
2017/12/26 PHP
PHP封装的mysqli数据库操作类示例
2019/02/16 PHP
运用jquery实现table单双行不同显示并能单行选中
2009/07/25 Javascript
jquery 跳到顶部和底部动画2句代码简单实现
2013/07/18 Javascript
禁止iframe页面的所有js脚本如alert及弹出窗口等
2014/09/03 Javascript
探讨JavaScript语句的执行过程
2016/01/28 Javascript
js基于myFocus实现轮播图效果
2017/02/14 Javascript
JQ中$(window).load和$(document).ready区别与执行顺序
2017/03/01 Javascript
JS实现线性表的链式表示方法示例【经典数据结构】
2017/04/11 Javascript
lhgcalendar时间插件限制只能选择三个月的实现方法
2017/07/03 Javascript
jQuery实现文本显示一段时间后隐藏的方法分析
2019/06/20 jQuery
详解vue中使用axios对同一个接口连续请求导致返回数据混乱的问题
2019/11/06 Javascript
JavaScript进制转换实现方法解析
2020/01/18 Javascript
javascript设计模式 ? 桥接模式原理与应用实例分析
2020/04/13 Javascript
react-intl实现React国际化多语言的方法
2020/09/27 Javascript
Python读取指定目录下指定后缀文件并保存为docx
2017/04/23 Python
python自动发邮件总结及实例说明【推荐】
2019/05/31 Python
Python调用Windows API函数编写录音机和音乐播放器功能
2020/01/05 Python
Django+python服务器部署与环境部署教程详解
2020/03/30 Python
Python基于argparse与ConfigParser库进行入参解析与ini parser
2021/02/02 Python
匡威比利时官网:Converse Belgium
2017/04/13 全球购物
Funko官方商店:源自美国,畅销全球搪胶收藏玩偶
2018/09/15 全球购物
世界领先的电子书网站:eBooks.com(在线购买小说、非小说和教科书)
2019/03/30 全球购物
描述JSP和Servlet的区别、共同点、各自应用的范围
2012/10/02 面试题
数控机床专业自荐信
2014/05/19 职场文书
秀!学妹看见都惊呆的Python小招数!【详细语言特性使用技巧】
2021/04/27 Python
python树莓派通过队列实现进程交互的程序分析
2021/07/04 Python