Chapter 36 Self-Assessment Quiz: Multithreading and Concurrent Programming

Test your understanding of threading concepts. Try to answer each question before revealing the answer.


Section 1: Multiple Choice

Q1. What method must you override in a TThread subclass to define the thread's work?

(a) Run (b) Execute (c) Start (d) Process

Answer (b) The Execute method is the thread's entry point. Override it to define what the thread does. Start is called to begin execution, but Execute is what you override.

Q2. What is the purpose of TCriticalSection?

(a) To create a new thread (b) To handle exceptions in threads (c) To ensure only one thread executes a code block at a time (d) To terminate a thread safely

Answer (c) A critical section (mutex) ensures mutual exclusion — when one thread has entered the section, all other threads that try to enter must wait until the first thread leaves. This prevents race conditions on shared data.

Q3. Two threads each try to increment a shared counter 1,000,000 times without synchronization. The expected final value is 2,000,000. What will the actual value most likely be?

(a) Exactly 2,000,000 (b) Less than 2,000,000 (c) More than 2,000,000 (d) A compilation error

Answer (b) Without synchronization, some increments will be lost due to the read-modify-write race condition. The actual value will be less than 2,000,000 — how much less depends on the timing.

Q4. Which method should you use to update a GUI label from a worker thread?

(a) Direct assignment: Label1.Caption := 'Done' (b) Synchronize(@UpdateLabel) or Queue(@UpdateLabel) (c) WriteLn('Done') (d) PostMessage(Handle, WM_SETTEXT, ...)

Answer (b) GUI controls must only be accessed from the main thread. Synchronize blocks the worker until the main thread executes the method. Queue is non-blocking. Direct assignment from a worker thread can corrupt GUI state or crash the application.

Q5. What happens if you call Lock.Enter without a matching Lock.Leave (e.g., because an exception occurs)?

(a) The lock is released automatically (b) The lock remains held — other threads waiting for it are blocked forever (deadlock) (c) The program crashes immediately (d) The lock is released after a timeout

Answer (b) If Lock.Leave is never called, the critical section remains locked. Any other thread that calls Lock.Enter will block forever. This is why lock acquisition must always be paired with release in a try..finally block.

Q6. What is the cooperative termination pattern for threads?

(a) Call KillThread from the main thread (b) Set Terminated := True and have the thread check it periodically in Execute (c) Raise an exception in the thread (d) Call Abort from outside the thread

Answer (b) The main thread calls Thread.Terminate which sets the Terminated property to True. The thread's Execute method checks Terminated periodically and exits gracefully when it is True. This is the only safe way to stop a thread.

Q7. Why is a thread pool more efficient than creating a new thread for every task?

(a) Thread pools use less memory per thread (b) Creating and destroying threads has significant OS overhead; reusing threads avoids it (c) Thread pools run faster than individual threads (d) Thread pools do not need synchronization

Answer (b) Each thread creation requires OS resource allocation (stack space, scheduling structures). For many short tasks, this overhead dominates the actual work. A thread pool creates threads once, reuses them for many tasks, and only destroys them at shutdown. Thread pools still need synchronization for the work queue.

Section 2: True/False

Q8. Setting FreeOnTerminate := True means you can safely call WaitFor on the thread.

Answer False. When FreeOnTerminate is True, the thread object is automatically freed when Execute returns. If you call WaitFor after the thread finishes, the object may already be freed, causing an access violation. Only use FreeOnTerminate for fire-and-forget threads where you never reference the thread object again.

Q9. WriteLn is generally safe to call from multiple threads in Free Pascal console applications.

Answer True. The Free Pascal RTL uses internal locking for standard I/O operations. However, output from multiple threads may be interleaved unpredictably. For GUI applications, console output is irrelevant — GUI controls must use Synchronize/Queue.

Q10. A deadlock can occur only when using two or more locks.

Answer False. While the classic deadlock involves two locks, a deadlock can also occur with a single non-reentrant lock if a thread tries to acquire the same lock twice (lock, then call a function that also tries to lock). TCriticalSection in Free Pascal is reentrant (the same thread can Enter multiple times), so this specific case does not deadlock with TCriticalSection, but it can with other lock types.

Section 3: Short Answer

Q11. Explain the snapshot pattern for thread-safe iteration. Why is iterating over a locked collection problematic even when each individual access is synchronized?

Answer Problem: even if Get(i) and Count are individually synchronized, the collection can change between calls. You might read Count=5, then another thread deletes an item, and Get(4) fails. Snapshot pattern: acquire the lock once, copy all items to a local array, release the lock, then iterate over the copy. The copy is private to the iterating thread and cannot be modified by others. The trade-off is the cost of the copy, but it eliminates all race conditions during iteration.

Q12. The PennyWise background sync thread uses both a critical section and Queue. Explain what each protects and why both are needed.

Answer The critical section protects shared data (FPendingExpenses, FStatus, FStatusMessage) that both the main thread and the worker thread access. It prevents race conditions on these fields. Queue is used for GUI updates — it schedules DoStatusChange to run in the main thread's context, because GUI controls cannot be accessed from the worker thread. Both are needed because they solve different problems: the critical section protects data integrity, while Queue ensures GUI safety.