You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于APScheduler同步机制的技术咨询:并发访问共享变量是否会导致竞态条件

Will My APScheduler Tasks Cause Race Conditions on Shared Variables?

Great question—let's unpack this clearly, since the APScheduler docs don't explicitly spell out the synchronization implications for shared state.

First: How APScheduler's BackgroundScheduler Runs Tasks

By default, the BackgroundScheduler uses a ThreadPoolExecutor (with a default of 10 worker threads). That means your step_up and step_heavy tasks run in separate threads when their intervals trigger. Any unprotected access to shared variables (like your self.x) is absolutely at risk of race conditions.

Why Your Current Code Is Vulnerable

Take the line self.x += 1—this looks atomic, but in Python it's actually three separate operations:

  1. Read the current value of self.x
  2. Add 1 to it
  3. Assign the new value back to self.x

If step_up runs while step_heavy is in the middle of its multiple self.x += -1 operations (especially during those slow np.random.random calls), the threads can overwrite each other's changes. For example:

  • step_heavy reads self.x = 5, starts a long numpy calculation
  • step_up reads self.x = 5, adds 1, sets it to 6
  • step_heavy resumes, subtracts 1 from its cached 5, sets self.x to 4
  • You end up with x=4 instead of the expected 5-1+1=5

How to Fix It: Add Thread Synchronization

You need to use a lock to ensure only one thread can modify or read self.x at a time. Python's threading.Lock is perfect for this. Here's how to modify your code:

import time
import numpy as np
from datetime import datetime
from apscheduler.schedulers.background import BackgroundScheduler
import threading

scheduler = BackgroundScheduler()

class Test(object):
    x = None
    def __init__(self):
        self.x = 0
        self.lock = threading.Lock()  # Add a thread lock

    def step_up(self):
        with self.lock:  # Auto-acquire/release lock
            self.x += 1
            ts = datetime.now().replace(microsecond=0)
            print(f'Step up {ts}: value of x={self.x}')

    def step_heavy(self):
        with self.lock:
            self.x += -1
            ts = datetime.now().replace(microsecond=0)
            print(f'Heavy 1 {ts}: value of x={self.x}')
        # Release lock during slow numpy calls (no shared state access here)
        np.random.random(size=(20000, 20000))
        with self.lock:
            self.x += -1
            ts = datetime.now().replace(microsecond=0)
            print(f'Heavy 2 {ts}: value of x={self.x}')
        np.random.random(size=(20000, 50000))
        with self.lock:
            self.x += -1
            ts = datetime.now().replace(microsecond=0)
            print(f'Heavy 3 {ts}: value of x={self.x}')

    def update_setpoint(self):
        pass

test = Test()
scheduler.add_job(test.step_up, 'interval', seconds=1)
scheduler.add_job(test.step_heavy, 'interval', seconds=15)
scheduler.start()

try:
    while True:
        time.sleep(2)
except (KeyboardInterrupt, SystemExit):
    scheduler.shutdown()

Key notes about this fix:

  • The with self.lock statement automatically manages lock acquisition/release, even if an error occurs in the block
  • We release the lock during slow numpy operations (since they don't touch self.x)—this keeps your step_up task responsive instead of waiting for heavy computations to finish

Final Verdict

Without explicit synchronization, your code will face race conditions. Adding a lock as shown ensures all accesses to self.x are atomic, eliminating unexpected value inconsistencies.

内容的提问来源于stack exchange,提问作者Jirka cigler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 13:02:31