У многих дома стоят многоядерные процессоры в компах. Круть крутяжная! А толку-то? Только-ли для того, чтобы пальцы гнуть пред одноклассниками? А для чего это все, и как это работает -- не хотели бы узнать?
Понятно, что чем больше ядер в процессоре, тем он быстрее работает. Это теоретически. А на самом деле как? Понятно, что каждое ядро процессора выполняет свой кусок кода одновременно с работой другого ядра. В этом случае удобно говорить не о ядрах и не о программах, а о потоках исполнения. И даже если у вас одноядерный процессор, потоки исполнения никуда не делись.
Поток -- это тот участок кода, который выполняется в данный момент времени. Вот такое вот несколько перпендикулярно направленное обывательской логике определение.
Итак...
Потоки исполнения
Потоки зиждутся на событиях. События могут быть как первичные:
* поступление электрических сигналов от каких-либо устройств,
* нажатие на клавиатуру, манипуляции с мышью,
* внутренний таймер или часы, ну т.д.
так и порожденные (вторичные)
* окончание процесса расчета какого-нибудь блока данных
* получение сообщения от другого потока
Первичные события порождают целую гроздь (куст, поток, дождь! -- называйте как хотите) вторичных событий. Вторичные события могут еще порождать себе подобных и т.д до тех пор, пока все не будет обработано.
Деятельность программы как раз и состоит в том, что на одно какое-то внешнее событие сделать какую-то конкретную работу. Нет событий -- нет и работы! Но в больших программах очень сложно сразу и в одном месте обработать первичное событие, поэтому большие проги расчленяют на кусочки, которые можно охватить -- на уровни, на потоки, на процессы, каждый из которых понятно как работает, легко отлаживаются и вообще осязаем. А взаимодействие этих кусочков основывается на передаче ими друг другу сообщений (о произошедших событиях).
Теперь, если такой кусочек кода запустить как отдельный процесс или поток, то он будет ждать сообщений, и только тогда, когда кусочек получает сообщение, он работает. В остальное время он стоит (ну или -- спит), т.е. не тратит мощь процессора. Но получив какое-то сообщение этот кусочек кода начинает бешено работать и может, в свою очередь, породить еще несколько сообщений, которые направит другим кускам кода. Ну прямо как государственная система: Президент подписал Указ и понеслась работа вниз по уровням, вплоть до конечных исполнителей! Ни ли они у нас (государство у программистов) стянули идею. Не важно 
Теперь давайте посмотрим на первичные события. Если у нас единственный источник событий, то не имеет смысла делить прогу на потоки. Все равно, пока не будет отработано предыдущее событие полностью, приступать к обработке следующего не имеет никакого смысла. Да, конечно, теоретически можно, наверно, что-то и сделать, но, если подумать, все это будет сделано за счет процессорного времени по отработке предыдущего события. Сначала мы должны приостановить отработку предыдущего события, потом переключить проц на отработку следующего, потом вернуться обратно... в общем, бардак в программе! Поэтому такие проги лучше делать однопоточными: закончили работы по отработке текущего события, приступили к отработке следующего. Нет событий -- спим!
Другое дело, когда у вас в проге имеется два или несколько источников первичных событий. Вот тут уже песня совсем другая! Оба (или несколько) событий нужно отрабатывать одновременно и параллельно. Если мы сможем переключаться с одного потока на другой быстро, то создастся впечатление одновременности их работы. Это понятно. И пока события еще не "повязаны" друг на друга, мы можем их одновременно отрабатывать двумя независимыми потоками.
Но в какой-то момент возникает условие невозможности продолжать работу по обработке события из первого источника, пока не будут готовы результаты обработки события из второго источника. (Я говорю про первичные источники, про первичные события, а не про порожденные вторичные события. Хотя и там тоже могут возникать коллизии).
Так вот плавно мы с вами подошли к теме синхронизации потоков. Я не могу тут в коротком своем эссе рассказать все детали этого механизма (И так вот сколько уже накатал!) Но я уверен, что теперь вы можете детали реализации многопоточных программ почерпнуть из многочисленных книг и тырнета.
Проиграйте все эти процессы, черкаясь на бумаге. Задайте себе вопросы, что должен делать поток в такой-то или такой ситуации -- продолжать ли выполнять свою функцию, или сваливаться в спячку.
И не бойтесь отправлять потоки в спячку. Назначение потоков -- все время спать и не "жрать" без надобности процессорного времени! Например, в циклах опроса готовности клавиатуры или какого-нибудь флага. И только время от времени при получении событий потоки должны просыпаться, быстро делать свою работу, и снова на печку -- спать!
Live Free or Die