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 相关文章推荐
详解使用pymysql在python中对mysql的增删改查操作(综合)
Jan 18 Python
Python面向对象程序设计中类的定义、实例化、封装及私有变量/方法详解
Feb 28 Python
Pytorch中的variable, tensor与numpy相互转化的方法
Oct 10 Python
Python数据可视化:饼状图的实例讲解
Dec 07 Python
Python操作Jira库常用方法解析
Apr 10 Python
Python基于os.environ从windows获取环境变量
Jun 09 Python
python文件读取失败怎么处理
Jun 23 Python
详解Python中第三方库Faker
Sep 25 Python
python中yield的用法详解
Jan 13 Python
聊聊Python中关于a=[[]]*3的反思
Jun 02 Python
解析python中的jsonpath 提取器
Jan 18 Python
Python Pandas解析读写 CSV 文件
Apr 11 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反转字符串函数strrev()函数的用法
2012/02/04 PHP
php使用Header函数,PHP_AUTH_PW和PHP_AUTH_USER做用户验证
2016/05/04 PHP
win10下 php安装seaslog扩展的详细步骤
2020/12/04 PHP
我的NodeJs学习小结(一)
2014/07/06 NodeJs
js 数组去重的四种实用方法
2014/09/09 Javascript
jquery实现滑动特效代码
2015/08/10 Javascript
读Javascript高性能编程重点笔记
2016/12/21 Javascript
深入学习jQuery中的data()
2016/12/22 Javascript
Zepto实现密码的隐藏/显示
2017/04/07 Javascript
深入理解node.js之path模块
2017/05/03 Javascript
nodeJS实现路由功能实例代码
2017/06/08 NodeJs
浅谈js的解析顺序 作用域 严格模式
2017/10/23 Javascript
全新打包工具parcel零配置vue开发脚手架
2018/01/11 Javascript
select2 ajax 设置默认值,初始值的方法
2018/08/09 Javascript
javascript json字符串到json对象转义问题
2019/01/22 Javascript
vue登录注册实例详解
2019/09/14 Javascript
JS实现音乐导航特效
2020/01/06 Javascript
Node.js设置定时任务之node-schedule模块的使用详解
2020/04/28 Javascript
解决vue无法侦听数组及对象属性的变化问题
2020/07/17 Javascript
python基础知识小结之集合
2015/11/25 Python
Python使用smtp和pop简单收发邮件完整实例
2018/01/09 Python
Python 微信爬虫完整实例【单线程与多线程】
2019/07/06 Python
python 返回一个列表中第二大的数方法
2019/07/09 Python
医务人员竞聘职务自我评价分享
2013/11/08 职场文书
放飞蜻蜓反思
2014/02/05 职场文书
宗教学大学生职业生涯规划范文
2014/02/08 职场文书
建筑安全责任书范本
2014/07/24 职场文书
2014党员学习兰辉先进事迹思想汇报
2014/09/17 职场文书
党员评议表自我评价范文
2014/10/20 职场文书
2014年专项整治工作总结
2014/11/17 职场文书
世界遗产导游词
2015/02/13 职场文书
校园环境卫生倡议书
2015/04/29 职场文书
2015年团队工作总结范文
2015/05/04 职场文书
高中地理教学反思
2016/02/19 职场文书
Python生成九宫格图片的示例代码
2021/04/14 Python
SQL Server数据库基本概念、组成、常用对象与约束
2022/03/20 SQL Server