单核心与双核心下badcnt.c的cnt结果范围是否存在差异?
badcnt.c: Dual-Core vs Uniprocessor Great question—this cuts to a fundamental concept in concurrent programming that Bryant emphasizes in Computer Systems: A Programmer's Perspective. Let's break down the behavior step by step, starting with your provided code:
The problematic code (
badcnt.c):#include "csapp.h" void *thread(void *vargp); volatile long cnt = 0; int main(int argc, char **argv){ long niters; pthread_t tid1, tid2; niters = atoi(argv[1]); Pthread_create(&tid1, NULL, thread, &niters); Pthread_create(&tid2, NULL, thread, &niters); Pthread_join(tid1, NULL); Pthread_join(tid2, NULL); printf("cnt = %ld\n", cnt); } void *thread(void *vargp){ long i; long niters = *((long *) vargp); for(i = 0; i < niters; i++) cnt++; return NULL; }
Recap: Uniprocessor Behavior (10 ≤ cnt ≤ 20)
You’re correct about the uniprocessor case. Here’s why the range is constrained to 10–20 (assuming niters=10):
- The
cnt++operation isn’t atomic—it translates to three distinct machine instructions: loadcntinto a register, add 1 to the register, store the new value back to memory. - On a single core, only one thread runs at a time. Thread switches can only occur between complete instructions, not mid-operation.
- Worst-case scenario: Thread 1 loads
cnt=0, then gets preempted. Thread 2 runs all 10 iterations, incrementingcntto 10. Thread 1 resumes, adds 1 to its cached value (0 → 1) and stores it, settingcnt=1. Thread 1 then finishes its remaining 9 iterations, each time incrementing the currentcntvalue, resulting in a final count of 10. - Best-case scenario: No preemption happens—each thread runs all 10 iterations consecutively, so
cntends at 20.
Dual-Core Behavior: Wider Range (1 ≤ cnt ≤ 20)
On a dual-core system, the result range does differ significantly—the lower bound can drop all the way to 1, while the upper bound stays at 20. The key difference is true parallel execution:
- On two cores, threads run simultaneously, not just interleaved. Both can execute their
cnt++instructions at the exact same moment. - Even with
volatile(which prevents compiler optimizations like cachingcntin a register), there’s no hardware synchronization. Two threads can both load the samecntvalue (e.g., 0) from memory (or their local CPU caches) at the same time. - Each thread adds 1 to their loaded value, then stores the result back. If both store the same value (e.g., 1), the second store overwrites the first—meaning two
cnt++operations only incrementcntby 1. - Extreme worst case: Both threads load the initial
cnt=0for all 10 iterations before either writes back. Then both threads add 1 to 0 (getting 1) and store 1 back 10 times each. All stores overwrite each other, leavingcnt=1as the final value.
Critical Takeaway
The root issue is that cnt++ is a non-atomic read-modify-write operation. Uniprocessors limit race conditions to interleaved execution, but multiprocessors allow true parallelism, leading to more frequent and severe race conditions that expand the possible range of cnt values. To fix this, you’d need to use synchronization primitives like mutexes (pthread_mutex_t) or atomic operations to ensure cnt++ happens atomically.
内容的提问来源于stack exchange,提问作者Taebum Kim

