【问题标题】:How to avoid race condition here如何在这里避免竞争条件
【发布时间】:2021-09-21 07:13:38
【问题描述】:

如果我使用线程运行它,getUrl 中是否会有竞争条件?我正在多个线程中更改data['key'] 的值。我需要将整个data 传递给我提出的请求,基本上,一组键将被固定,只有名为key 的键会因我发出的每个线程调用而改变

import requests
from concurrent.futures import ThreadPoolExecutor


def getUrl(url, value):
    data['key'] = value # will there be a race condition here
    return requests.get(url, data=data).text # and when the value is passed here

data = {'key': 1, 'fixedKey': 'fixedValue', 'fixedKey2': 'fixedValue2'}
resultArray = []
threadPool = ThreadPoolExecutor(32)

for i in range(100):
    resultArray.append(threadPool.submit(getUrl, 'https://google.com', i))

Thread Safety in Python's dictionary 我检查了这个,但我的困惑是当我在data['key'] = value 中进行设置时线程切换上下文,然后其他一些线程更改了它,下一行现在具有由不同线程设置的新值.

例子

Value set by thread 1
data['key'] = 1
Context Switch
Value set by thread 2
data['key'] = 2
Context Switch back to old thread 1
is data['key'] = `2` now? I would necessarily want the value `1`

如果我使用锁,那么我将在这里失去并发性。

【问题讨论】:

  • 为什么要更改函数内部的值?为什么不在 for 循环本身@Jake 中做呢?
  • 如果我错了请纠正我,我不能 100% 确定所有函数调用是否会在执行后使用不同的值@Vishnudev
  • 他们可能会,他们可能不会。取决于很多事情。为了确保它们不使用更改的值,请尝试在函数之外进行。

标签: python


【解决方案1】:

存在竞争条件,因为 data 在线程之间共享,并且您从它们中对其进行了变异。

可视化这种竞争条件的一种简单方法是使用sleepprint 模拟request 调用:

from concurrent.futures import ThreadPoolExecutor
from time import sleep

def getUrl(url, value):
    data['key'] = value
    sleep(0.3)
    print(value, data["key"])

data = {'key': 1, 'fixedKey': 'fixedValue', 'fixedKey2': 'fixedValue2'}
resultArray = []
threadPool = ThreadPoolExecutor(32)

for i in range(100):
    resultArray.append(threadPool.submit(getUrl, 'https://google.com', i))

您会看到传递给getUrl 的值有时与存储在data 中的值不同。

一种解决方案是在对其进行变异之前将data 复制到本地。

【讨论】:

  • 谢谢@louislac 不幸的是,由于没有足够的分数,我无法为您的答案投票,但我确实找到了您将比赛条件可视化的新方法和值得记住的东西
【解决方案2】:

您当前的代码确实存在竞争条件,因为您正在不同线程中更改和使用相同的 global 变量。一种简单的方法是使用本地副本:

def getUrl(url, value):
    data2 = dict(data, 'key' = value)         # build a local instance
    return requests.get(url, data=data2).text # and use it: no race condition

【讨论】:

  • 谢谢,那么每个线程调用都会有自己的函数范围变量副本吗?在我接受这个好建议之前,为每次迭代创建一个副本会占用大量内存吗?
  • @Jake:是的,线程启动前创建的变量是共享的,线程中创建的变量是线程本地的(每个线程都有自己的副本)。每个线程有一个副本确实会消耗内存,但从某种意义上说,您正在更改内存以提高速度,这是一种常见的平衡...
  • 我需要使用with lock:data2 = dict(data, 'key' = value) 还是不需要锁定?
  • 不需要锁,因为原始/全局字典永远不会被修改。
猜你喜欢
  • 1970-01-01
  • 2019-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-30
  • 2010-09-25
  • 2016-09-20
相关资源
最近更新 更多