the critical-section problem
Once you know that shared data must be touched inside a protected critical section, the obvious next question is: what exactly must a good protection scheme guarantee? Just keeping people out is not enough — a scheme that simply locked the bathroom door forever would 'prevent collisions' while also being useless. The critical-section problem is the precise statement of what a correct entry/exit protocol must achieve. It was framed by Dijkstra, and any real solution — a lock, a semaphore, a clever software trick — must satisfy all three of its requirements.
The three requirements are: Mutual exclusion — if one thread is executing in its critical section, no other thread may be in its own critical section at the same time. Progress — if no thread is in its critical section and one or more threads want to enter, then the choice of who goes next cannot be postponed indefinitely, and only threads actually contending may take part in that decision (a thread idling in its remainder section must not block others). Bounded waiting — there is a limit on how many times other threads can enter their critical sections after a thread has requested entry and before that request is granted, so no thread waits forever. Mutual exclusion is the safety property (nothing bad happens); progress and bounded waiting are liveness properties (something good eventually happens, and fairly).
Why three separate rules? Because it is easy to satisfy one and quietly violate another, and each missing rule is a real bug. A scheme with mutual exclusion but no progress can deadlock or leave the resource idle while threads needlessly wait. A scheme with mutual exclusion and progress but no bounded waiting can starve an unlucky thread forever while others keep cutting ahead. A correct solution must also make no unrealistic assumptions — for example, it must not depend on threads running at any particular relative speed, since the scheduler is free to pause any thread at any moment. The three requirements together are the contract every synchronization primitive promises to keep.
A naive 'take turns' lock (a shared variable turn that two threads alternate) gives mutual exclusion, but it fails progress: if thread 0 finishes and never wants the resource again, thread 1 is stuck forever waiting for turn to flip back to it, even though the resource is free.
A real solution must meet all three: mutual exclusion, progress, and bounded waiting.
Mutual exclusion alone is necessary but not sufficient. A scheme can be perfectly mutually exclusive and still deadlock (no progress) or starve a thread (no bounded waiting).