Python 3线程与Python 2性能差异及上下文切换问题咨询
Let’s break down what’s going on here, and why you’re seeing such a stark difference between Python 2.7 and Python 3.6.
The Core Issue: GIL Scheduling Changes
The root cause lies in how Python’s Global Interpreter Lock (GIL) works across versions. The GIL ensures only one Python thread executes bytecode at a time, but its scheduling logic changed significantly after Python 2.7:
- Python 2.7: The GIL was released after every 100 bytecode instructions. Even if your main thread was stuck in a
while ... passbusy-wait, it would regularly drop the GIL, letting yourthread_itthread jump in and update the flag quickly. That’s why you saw consistent ~15ms runtimes. - Python 3.2+: The GIL switched to a time-based scheduling system. By default, a thread holds the GIL for up to 5ms before being forced to release it. But tight CPU-bound loops like
while ... passcan bypass this check — your main thread ends up hogging the GIL, preventingthread_itfrom getting a chance to set the flag, hence the 10ms delay you’re seeing.
Why sleep(0) or print() Fixes It
When you add systime.sleep(0) or print("") to those while loops, you’re forcing the thread to give up the GIL immediately:
sleep(0): Even though it doesn’t pause execution, this call tells the OS scheduler the thread is willing to yield CPU time. In Python, this triggers an explicit GIL release, letting other threads run.print(""): Any I/O operation (like writing to stdout) causes the GIL to be released, since Python has to wait for the OS to handle the I/O. This gives yourthread_itthread a window to update the flag.
Moving these operations to an external function doesn’t help because the busy-wait loop itself is still running in the main thread, holding onto the GIL until that external function call triggers a release.
The Better Fix: Ditch Busy-Waits for Proper Synchronization
Busy-waiting with while ... pass is almost never the right approach for thread synchronization. Python’s threading module has built-in tools designed for exactly this kind of signaling — like threading.Event().
Here’s a revised version of your code using Event instead of manual flag checks:
# -*- coding: utf-8 -*- import time as systime import timeit import threading thread_flag = False counter = 0 AVERAGE = 10 # Use an Event for thread-safe signaling flag_event = threading.Event() def thread_it(): global thread_flag while not thread_flag: # Wait until the event is cleared (instead of looping on a flag) flag_event.wait() systime.sleep(0.05) # Signal the main thread we're ready flag_event.set() def to_time_it(): global counter # Clear the event to let the thread proceed flag_event.clear() systime.sleep(0.03) # Wait for the thread to signal us (no busy-wait!) flag_event.wait() print("OK: %s" % counter) # Reset for the next cycle flag_event.clear() counter += 1 def main(): global thread_flag print("Threading while loop test") th = threading.Thread(target=thread_it) th.daemon = True # Let the thread exit when the main program does th.start() tajm = timeit.timeit(to_time_it, number=AVERAGE) * 1000 / AVERAGE systime.sleep(0.7) # Shutdown the thread thread_flag = True flag_event.set() # Wake the thread so it can exit the loop print("\n\tAverage time: %.2f ms" % tajm) if __name__ == '__main__': main()
This code runs consistently fast across Python 2 and 3 because:
Event.wait()puts the thread into a blocked state instead of spinning on the CPU. It doesn’t hold the GIL, so other threads can run freely.- Signaling between threads is handled at the OS level, which is far more efficient and reliable than manual flag polling.
Key Takeaways
- Python 3’s GIL scheduling is more efficient for most workloads, but it penalizes tight CPU-bound busy-waits.
sleep(0)and I/O operations force GIL releases, but they’re workarounds, not proper solutions.- Always use Python’s built-in synchronization primitives (
Event,Condition,Lock) for thread communication — they’re designed to avoid these exact problems.
内容的提问来源于stack exchange,提问作者schperplata

