찾다
백엔드 개발파이썬 튜토리얼在Python中测试访问同一数据的竞争条件的方法

当你有多个进程或线程访问相同的数据时,竞争条件是一个威胁。本文探讨了在发现竞争条件后如何测试它们。

Incrmnt

你在一个名为“Incrmnt”的火热新创公司工作,该公司只做一件事情,并且做得比较好。

你展示一个全局计数器和一个加号,用户可以点击加号,此时计数器加一。这太简单了,而且容易使人上瘾。毫无疑问这就是接下来的大事情。

投资者们争先恐后的进入了董事会,但你有一个大问题。

竞争条件

在你的内测中,Abraham和Belinda是如此的兴奋,以至于每个人都点了100次加号按钮。你的服务器日志显示了200次请求,但计数器却显示为173。很明显,有一些请求没有被加上。

先将“Incrmnt变成了一坨屎”的新闻抛到脑后,你检查下代码(本文用到的所有代码都能在Github上找到)。
 

# incrmnt.py
import db
 
def increment():
  count = db.get_count()
 
  new_count = count + 1
  db.set_count(new_count)
 
  return new_count

你的Web服务器使用多进程处理流量请求,所以这个函数能在不同的线程中同时执行。如果你没掌握好时机,将会发生:
 

# 线程1和线程2在不同的进程中同时执行
# 为了展示的目的,在这里并排放置
# 在垂直方向分开它们,以说明在每个时间点上执行什么代码
# Thread 1(线程1)         # Thread 2(线程2)
def increment():
                  def increment():
  # get_count returns 0
  count = db.get_count()
                    # get_count returns 0 again
                    count = db.get_count()
  new_count = count + 1
  # set_count called with 1
  db.set_count(new_count)
                    new_count = count + 1
                    # set_count called with 1 again
                    db.set_count(new_count)

所以尽管增加了两次计数,但最终只增加了1。

你知道你可以修改这个代码,变为线程安全的,但是在你那么做之前,你还想写一个测试证明竞争的存在。

重现竞争

在理想情况下,测试应该尽可能的重现上面的场景。竞争的关键因素是:

?两个 get_count 调用必须在两个 set_count 调用之前执行,从而使得两个线程中的计数具有相同的值。

set_count 调用,什么时候执行都没关系,只要它们都在 get_count 调用之后即可。

简单起见,我们试着重现这个嵌套的情形。这里整 个Thread 2 在 Thread 1 的首个 get_count 调用之后执行:

 

# Thread 1             # Thread 2
def increment():
  # get_count returns 0
  count = db.get_count()
                  def increment():
                    # get_count returns 0 again
                    count = db.get_count()
 
                    # set_count called with 1
                    new_count = count + 1
                    db.set_count(new_count)
  # set_count called with 1 again
  new_count = count + 1
  db.set_count(new_count)

before_after 是一个库,它提供了帮助重现这种情形的工具。它可以在一个函数之前或之后插入任意代码。

before_after 依赖于 mock 库,它用来补充一些功能。如果你不熟悉 mock,我建议阅读一些优秀的文档。文档中特别重要的部分是 Where To Patch。

我们希望,Thread 1 调用 get_count 后,执行全部的 Thread 2 ,之后恢复执行 Thread 1。

我们的测试代码如下:
 

# test_incrmnt.py
 
import unittest
 
import before_after
 
import db
import incrmnt
 
class TestIncrmnt(unittest.TestCase):
  def setUp(self):
    db.reset_db()
 
  def test_increment_race(self):
    # after a call to get_count, call increment
    with before_after.after('incrmnt.db.get_count', incrmnt.increment):
      # start off the race with a call to increment
      incrmnt.increment()
 
    count = db.get_count()
    self.assertEqual(count, 2)

在首次 get_count 调用之后,我们使用 before_after 的上下文管理器 after 来插入另外一个 increment 的调用。

在默认情况下,before_after只调用一次 after 函数。在这个特殊的情况下这是很有用的,因为否则的话堆栈会溢出(increment调用get_count,get_coun t也调用 increment,increment 又调用get_count…)。

这个测试失败了,因为计数等于1,而不是2。现在我们有一个重现了竞争条件的失败测试,一起来修复。

防止竞争

我们将要使用一个简单的锁机制来减缓竞争。这显然不是理想的解决方案,更好的解决方法是使用原子更新进行数据存储——但这种方法能更好地示范 before_after 在测试多线程应用程序上的作用。

在 incrmnt.py 中添加一个新函数:

# incrmnt.py
 
def locking_increment():
  with db.get_lock():
    return increment()

它保证在同一时间只有一个线程对计数进行读写操作。如果一个线程试图获取锁,而锁被另外一个线程保持,将会引发 CouldNotLock 异常。

现在我们增加这样一个测试:
 

# test_incrmnt.py
 
def test_locking_increment_race(self):
  def erroring_locking_increment():
    # Trying to get a lock when the other thread has it will cause a
    # CouldNotLock exception - catch it here or the test will fail
    with self.assertRaises(db.CouldNotLock):
      incrmnt.locking_increment()
 
  with before_after.after('incrmnt.db.get_count', erroring_locking_increment):
    incrmnt.locking_increment()
 
  count = db.get_count()
  self.assertEqual(count, 1)

现在在同一时间,就只有一个线程能够增加计数了。

减缓竞争

我们这里还有一个问题,通过上边这种方式,如果两个请求冲突,一个不会被登记。为了缓解这个问题,我们可以让 increment 重新链接服务器(有一个简洁的方式,就是用类似 funcy retry 的东西):
 

# incrmnt.py
 
def retrying_locking_increment():
  @retry(tries=5, errors=db.CouldNotLock)
  def _increment():
    return locking_increment()
 
  return _increment()

当我们需要比这种方法提供的更大规模的操作时,可以将 increment 作为一个原子更新或事务转移到我们的数据库中,让其在远离我们的应用程序的地方承担责任。

总结

Incrmnt 现在不存在竞争了,人们可以愉快地点击一整天,而不用担心自己不被计算在内。

这是一个简单的例子,但是 before_after 可以用于更复杂的竞争条件,以确保你的函数能正确地处理所有情形。能够在单线程环境中测试和重现竞争条件是一个关键,它能让你更确定你正在正确地处理竞争条件。

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
파이썬과 시간 : 공부 시간을 최대한 활용파이썬과 시간 : 공부 시간을 최대한 활용Apr 14, 2025 am 12:02 AM

제한된 시간에 Python 학습 효율을 극대화하려면 Python의 DateTime, Time 및 Schedule 모듈을 사용할 수 있습니다. 1. DateTime 모듈은 학습 시간을 기록하고 계획하는 데 사용됩니다. 2. 시간 모듈은 학습과 휴식 시간을 설정하는 데 도움이됩니다. 3. 일정 모듈은 주간 학습 작업을 자동으로 배열합니다.

파이썬 : 게임, Guis 등파이썬 : 게임, Guis 등Apr 13, 2025 am 12:14 AM

Python은 게임 및 GUI 개발에서 탁월합니다. 1) 게임 개발은 Pygame을 사용하여 드로잉, 오디오 및 기타 기능을 제공하며 2D 게임을 만드는 데 적합합니다. 2) GUI 개발은 Tkinter 또는 PYQT를 선택할 수 있습니다. Tkinter는 간단하고 사용하기 쉽고 PYQT는 풍부한 기능을 가지고 있으며 전문 개발에 적합합니다.

Python vs. C : 응용 및 사용 사례가 비교되었습니다Python vs. C : 응용 및 사용 사례가 비교되었습니다Apr 12, 2025 am 12:01 AM

Python은 데이터 과학, 웹 개발 및 자동화 작업에 적합한 반면 C는 시스템 프로그래밍, 게임 개발 및 임베디드 시스템에 적합합니다. Python은 단순성과 강력한 생태계로 유명하며 C는 고성능 및 기본 제어 기능으로 유명합니다.

2 시간의 파이썬 계획 : 현실적인 접근2 시간의 파이썬 계획 : 현실적인 접근Apr 11, 2025 am 12:04 AM

2 시간 이내에 Python의 기본 프로그래밍 개념과 기술을 배울 수 있습니다. 1. 변수 및 데이터 유형을 배우기, 2. 마스터 제어 흐름 (조건부 명세서 및 루프), 3. 기능의 정의 및 사용을 이해하십시오. 4. 간단한 예제 및 코드 스 니펫을 통해 Python 프로그래밍을 신속하게 시작하십시오.

파이썬 : 기본 응용 프로그램 탐색파이썬 : 기본 응용 프로그램 탐색Apr 10, 2025 am 09:41 AM

Python은 웹 개발, 데이터 과학, 기계 학습, 자동화 및 스크립팅 분야에서 널리 사용됩니다. 1) 웹 개발에서 Django 및 Flask 프레임 워크는 개발 프로세스를 단순화합니다. 2) 데이터 과학 및 기계 학습 분야에서 Numpy, Pandas, Scikit-Learn 및 Tensorflow 라이브러리는 강력한 지원을 제공합니다. 3) 자동화 및 스크립팅 측면에서 Python은 자동화 된 테스트 및 시스템 관리와 ​​같은 작업에 적합합니다.

2 시간 안에 얼마나 많은 파이썬을 배울 수 있습니까?2 시간 안에 얼마나 많은 파이썬을 배울 수 있습니까?Apr 09, 2025 pm 04:33 PM

2 시간 이내에 파이썬의 기본 사항을 배울 수 있습니다. 1. 변수 및 데이터 유형을 배우십시오. 이를 통해 간단한 파이썬 프로그램 작성을 시작하는 데 도움이됩니다.

10 시간 이내에 프로젝트 및 문제 중심 방법에서 컴퓨터 초보자 프로그래밍 기본 사항을 가르치는 방법?10 시간 이내에 프로젝트 및 문제 중심 방법에서 컴퓨터 초보자 프로그래밍 기본 사항을 가르치는 방법?Apr 02, 2025 am 07:18 AM

10 시간 이내에 컴퓨터 초보자 프로그래밍 기본 사항을 가르치는 방법은 무엇입니까? 컴퓨터 초보자에게 프로그래밍 지식을 가르치는 데 10 시간 밖에 걸리지 않는다면 무엇을 가르치기로 선택 하시겠습니까?

중간 독서를 위해 Fiddler를 사용할 때 브라우저에서 감지되는 것을 피하는 방법은 무엇입니까?중간 독서를 위해 Fiddler를 사용할 때 브라우저에서 감지되는 것을 피하는 방법은 무엇입니까?Apr 02, 2025 am 07:15 AM

Fiddlerevery Where를 사용할 때 Man-in-the-Middle Reading에 Fiddlereverywhere를 사용할 때 감지되는 방법 ...

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
1 몇 달 전By尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

안전한 시험 브라우저

안전한 시험 브라우저

안전한 시험 브라우저는 온라인 시험을 안전하게 치르기 위한 보안 브라우저 환경입니다. 이 소프트웨어는 모든 컴퓨터를 안전한 워크스테이션으로 바꿔줍니다. 이는 모든 유틸리티에 대한 액세스를 제어하고 학생들이 승인되지 않은 리소스를 사용하는 것을 방지합니다.

에디트플러스 중국어 크랙 버전

에디트플러스 중국어 크랙 버전

작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

DVWA

DVWA

DVWA(Damn Vulnerable Web App)는 매우 취약한 PHP/MySQL 웹 애플리케이션입니다. 주요 목표는 보안 전문가가 법적 환경에서 자신의 기술과 도구를 테스트하고, 웹 개발자가 웹 응용 프로그램 보안 프로세스를 더 잘 이해할 수 있도록 돕고, 교사/학생이 교실 환경 웹 응용 프로그램에서 가르치고 배울 수 있도록 돕는 것입니다. 보안. DVWA의 목표는 다양한 난이도의 간단하고 간단한 인터페이스를 통해 가장 일반적인 웹 취약점 중 일부를 연습하는 것입니다. 이 소프트웨어는

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.