Concurrency lets multiple activities make progress without requiring each to finish before the next begins. On one core, task switching can create that effect by alternating between activities; multiple cores can execute them in parallel. The distinction matters: behavior observed under task switching may not expose incorrect assumptions about memory. section 1.1.1
Figure 1.1. Two approaches to concurrency: parallel execution on a dual-core machine versus task switching on a single-core machine
Compare alternating execution on one core with parallel execution on two cores. Both support concurrent activities, but only the latter runs them at the same instant. section 1.1.1
An application can organize concurrent work in two basic ways:
- Use separate processes that communicate through mechanisms such as sockets, files, or pipes. Process isolation protects data, but communication and process creation have costs. section 1.1.2
- Use multiple threads in one process. Shared resources make communication easier, but they also require care when threads use those resources. section 1.1.2
Choose concurrency to separate responsibilities or to improve performance. A user-interface thread can handle input while another thread does lengthy work. For throughput, task parallelism divides work into parts; data parallelism applies the same operation to different parts of the data. The term parallelism emphasizes performance, while concurrency often emphasizes separation of concerns or responsiveness; the uses overlap. section 1.1.3 section 1.2.1 section 1.2.2
Keep an event loop responsive
A GUI loop repeatedly gets an event and processes it. If that same thread performs a lengthy task, it must interrupt the task to check for input. Instead, start a task thread on a start event and leave the GUI thread to process events. A stop event sets a cancellation flag and joins the task thread; successful completion posts an event so the GUI handler can join the thread and display results. section 8.4.4
Listing 8.6. Separating GUI thread from task thread
std::thread task_thread;
std::atomic<bool> task_cancelled(false);
void gui_thread()
{
while(true)
{
event_data event=get_event();
if(event.type==quit)
break;
process(event);
}
}
void task()
{
while(!task_complete() && !task_cancelled)
{
do_next_operation();
}
if(task_cancelled)
{
perform_cleanup();
}
else
{
post_gui_event(task_complete);
}
}
void process(event_data const& event)
{
switch(event.type)
{
case start_task:
task_cancelled=false;
task_thread=std::thread(task);
break;
case stop_task:
task_cancelled=true;
task_thread.join();
break;
case task_complete:
task_thread.join();
display_results();
break;
default:
//...
}
}
Follow the start, stop, and completion cases alongside the worker’s cancellation check to see where the two threads interact. section 8.4.4
Do not add threads automatically. Thread creation consumes time and resources, and too many threads can slow a system or exhaust available resources. Concurrency also makes code harder to understand and maintain. Use it when responsiveness, clearer separation, or expected performance gains justify those costs; designing the interactions correctly is the difficult part. section 1.2.3 section 1.5






















