Python 避免并发访问同一队列元素

Python 避免并发访问同一队列元素,python,django,Python,Django,我正在审查/重构一个队列,该队列将由最多20人同时在内部使用,但到目前为止,多人可以访问第一个元素(我们尝试在本地同时单击链接) 通量与此类似: 视图.py [GET] def list_operator(request, id): if request.POST: utilitary = Utilitary(id) pool = ThreadPool(processes=1) async_result = pool.apply_async(utilitary.rec

我正在审查/重构一个队列,该队列将由最多20人同时在内部使用,但到目前为止,多人可以访问第一个元素(我们尝试在本地同时单击链接)

通量与此类似:

视图.py

[GET]
def list_operator(request, id):
  if request.POST:
    utilitary = Utilitary(id)
    pool = ThreadPool(processes=1)
    async_result = pool.apply_async(utilitary.recover_people, (id, ))
    return_val = async_result.get()
    person = People.objects.get(pk=return_val)
    return redirect('people:people_update', person.pk)
实用性.py

[GET]
def list_operator(request, id):
  if request.POST:
    utilitary = Utilitary(id)
    pool = ThreadPool(processes=1)
    async_result = pool.apply_async(utilitary.recover_people, (id, ))
    return_val = async_result.get()
    person = People.objects.get(pk=return_val)
    return redirect('people:people_update', person.pk)
此文件具有方法
recover\u people
,该方法在多个表中执行大约4-5次查询(其中people具有
flag\u allocated=False
),并对列表进行排序,以返回第一个元素。 最后一步是:

for person in people:
  p_address = People_Address.objects.get(person_id=person.id)
  p_schedule = Schedules.objects.get(schedules_state=p_address.p_state)

  if datetime.now() > parse_time(p_schedule.schedules_hour):
    person = People.objects.get(pk=person.id)
    person.flag_allocated = True
    person.date_of_allocation = datetime.now()
    person.save()
    return person.pk
也许实用方法的逻辑有问题?或者我应该预料到有这么多人同时调用这个方法会出现这个问题


使用缓存有帮助吗?对不起,我是django和mvc的新手。

如果你不采取任何措施防止并发进程之间的干扰,它们迟早会互相攻击

在数据库中对队列进行建模的一种由来已久的方法是,在执行作业之前,以事务一致的方式将工作线程注册到特定作业

假设您有一个表
work
,其中列表示作业id或规范,初始状态为null,而worker的初始值为null。工人可以通过运行更新来“注册”作业,如

 Update `work` set worker = my_worker_id, status=initializing where status is null and worker is null limit 1.
由于“where”条款,只有一名员工可以“注册”下一个作业


这并不是完美的——你仍然需要处理那些被一个失败的工人遗弃的工作。状态栏在作业完成时更新,与工人的某种心跳相结合,并围绕作业幂等性进行仔细设计,将为您提供原语,以确保作业不会被失败或擅离职守的工作人员卡住。

这里的标准解决方案是使用某种锁,这样您就不能同时执行两次实用的
。recover\u people
无论什么-函数将等待,直到获得锁、执行并释放锁

考虑到Django通常由多个进程提供服务(您当然不想改变这一点),并且您不想让一个糟糕的调用永远保持锁,这里一个好的解决方案是使用redis之类的东西来存储锁(所有Django进程当然共享同一个redis db),将到期时间设置为合理的时间,这样它就不会永远保持不变

有一些使用芹菜的例子(这里你不一定需要芹菜,只是原理是一样的,因为这是使用芹菜来避免并发任务相互干扰的常见用例)


您也可以只使用SQL数据库来存储锁,但这样就不会自动过期

在这种情况下,再次选择数据库中的“person”似乎有很多,只是为了将其锁定在内存中,然后将其写入数据库。这个动作不是原子的

您可以在以下操作之间让其他进程锁定记录:

这就是你的问题所在。但是如果您直接更新数据库上的记录,并传递一个条件,在该条件下,更新只会写入
标志\u allocated=False
的记录,那么您只需查看您的更新是否影响了任何行。如果不是,则转到队列中的下一个人

比如:

for person in people:
    rows = People.objects.filter(pk=person.id, flag_allocated=False).update(flag_allocated=True)
    if rows:
        break # Got the person... And nobody else will.

更新将锁定记录以将其写入分配标志(SQL原则)。如果两个更新试图弄乱同一行,一个会先做,然后第二个不会更新任何内容,会尝试下一个人。

我觉得这绝对像是一个比赛条件。它们都运行相同的查询,得到相同的结果,并且很可能执行相同的工作。您可以尝试考虑一些方法,在每个工作单元中“注册”一个工人work@DanFarrell对不起,你能提供一个例子或链接吗?这对我来说是非常新的。这实际上是一个“队列”吗?看看我的回答是否对你说是的,但是你对
实用程序的调用。恢复人员
没有排队-他们是由并发进程运行的,因为
实用程序。恢复人员
不是一个原子操作,你确实有一个竞争条件(同时运行
实用程序.recover\u people
的两个进程将选择相同的对象)。那么也许芹菜之类的东西会有帮助吗?我绝对建议添加一个排队框架。我们过去在我的工作中使用芹菜,并发现它是可以接受的。我也使用redis进行排队,并且发现它比关系数据库更适合。