Separation of concerns keeps related code together and unrelated code apart. When distinct responsibilities must run at the same time, separate threads can let each responsibility proceed without repeatedly handing control to the others. The threads still interact where their tasks require it. section 1.2.1
Playback and user input
A DVD player must decode and play media while responding to Pause or Quit. In one thread, playback code must regularly check for input. With separate threads, playback and the user interface can follow their own control flow; a Pause request becomes a direct interaction between them. The interface can respond promptly even if playback work is still underway. section 1.2.1
This is division by task type, not division of identical work across chunks of data. Each thread specializes in a responsibility, such as handling incoming events or doing ongoing work. It avoids a single loop that must run a little of every task and make each task save its state before yielding. Separation does not eliminate coordination: shared data can still complicate reasoning. section 8.1.3 section 4.4.2
An ATM split by responsibility
One thread can control the physical machinery, another the ATM logic, and a third bank communication. They can exchange messages instead of sharing application data. The logic then reacts to received events, updates its state, and sends requests to the appropriate thread. In C++, keeping data private this way requires discipline because threads share an address space. section 4.4.2
Listing 4.15. A simple implementation of an ATM logic class
struct card_inserted
{
std::string account;
};
class atm
{
messaging::receiver incoming;
messaging::sender bank;
messaging::sender interface_hardware;
void (atm::*state)();
std::string account;
std::string pin;
void waiting_for_card() #1
{
interface_hardware.send(display_enter_card()); #2
incoming.wait() #3
.handle<card_inserted>(
[&](card_inserted const& msg) #4
{
account=msg.account;
pin="";
interface_hardware.send(display_enter_pin());
state=&atm::getting_pin;
}
);
}
void getting_pin();
public:
void run() #5
{
state=&atm::waiting_for_card; #6
try
{
for(;;)
{
(this->*state)(); #7
}
}
catch(messaging::close_queue const&)
{
}
}
};
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":12,\"ch\":57},{\"line\":12,\"ch\":59}],[{\"line\":14,\"ch\":57},{\"line\":14,\"ch\":59}],[{\"line\":15,\"ch\":57},{\"line\":15,\"ch\":59}],[{\"line\":17,\"ch\":57},{\"line\":17,\"ch\":59}],[{\"line\":28,\"ch\":57},{\"line\":28,\"ch\":59}],[{\"line\":30,\"ch\":57},{\"line\":30,\"ch\":59}],[{\"line\":35,\"ch\":57},{\"line\":35,\"ch\":59}]]"}
!@%STYLE%@!
The ATM logic class makes the boundary concrete: it waits for an incoming message, handles a card-inserted event, sends a display request, and changes its state. section 4.4.2





















