(Как и Джон Скит, большая часть этого предполагает .NET)
Имея риск показаться спорным, комментарии, подобные этим, просто беспокоят меня:
Учимся писать многопоточные
программы правильно крайне
сложно и отнимает много времени.
Темы следует избегать, когда
возможно ...
Практически невозможно написать программное обеспечение, которое делает что-либо существенное, не используя потоки в некотором объеме. Если вы работаете в Windows, откройте диспетчер задач, включите столбец «Количество потоков», и вы, вероятно, сможете посчитать с одной стороны количество процессов, которые используют один поток. Да, не следует просто использовать потоки ради использования потоков и не следует делать это кавалерно, но, честно говоря, я считаю, что эти штампы используются слишком часто.
Если бы мне пришлось сводить многопоточное программирование к истинному новичку, я бы сказал так:
- Прежде чем перейти к нему, сначала поймите, что граница класса не совпадает с границей потока. Например, если метод обратного вызова в вашем классе вызывается другим потоком (например, делегатом AsyncCallback методу TcpListener.BeginAcceptTcpClient ()), следует понимать, что обратный вызов выполняет в этого другого потока. Таким образом, даже если обратный вызов происходит для того же объекта, вам все равно придется синхронизировать доступ к членам объекта в методе обратного вызова. Потоки и классы ортогональны; важно понять этот момент.
- Определите, какие данные должны быть разделены между потоками. После того, как вы определили общие данные, попробуйте объединить их в один класс, если это возможно.
- Ограничьте места, где общие данные могут быть записаны и прочитаны. Если вы сможете свалить это в одно место для письма и в одно место для чтения, вы окажете себе огромную услугу. Это не всегда возможно, но это хорошая цель для стрельбы.
- Очевидно, что вы синхронизируете доступ к общим данным, используя класс Monitor или ключевое слово lock.
- Если возможно, используйте один объект для синхронизации ваших общих данных независимо от того, сколько существует различных общих полей. Это упростит вещи. Однако это также может чрезмерно ограничивать вещи, и в этом случае вам может понадобиться объект синхронизации для каждого общего поля. И в этот момент использование неизменяемых классов становится очень удобным.
- Если у вас есть один поток, который должен сигнализировать о другом потоке, я настоятельно рекомендую использовать класс ManualResetEvent, чтобы сделать это вместо использования событий / делегатов.
Подводя итог, я бы сказал, что многопоточность не сложна, но она может быть утомительной. Тем не менее, приложение с правильной резьбой будет более отзывчивым, и ваши пользователи будут очень благодарны.
EDIT:
В ThreadPool.QueueUserWorkItem (), асинхронных делегатах, различных парах методов BeginXXX / EndXXX и т. Д. В C # нет ничего «чрезвычайно сложного». Во всяком случае, эти методы делают намного проще для выполнения различных задач в многопоточном режиме. Если у вас есть приложение с графическим интерфейсом, которое выполняет какие-либо тяжелые взаимодействия с базой данных, сокетами или вводом / выводом, практически невозможно заставить внешний интерфейс реагировать на пользователя без использования потоков за кулисами. Методы, которые я упомянул выше, делают это возможным и легким в использовании. Важно понимать подводные камни, чтобы быть уверенным. Я просто считаю, что мы делаем программистов, особенно младших, плохой услугой, когда говорим о том, насколько «чрезвычайно сложным» является многопоточное программирование или как «избегать потоков». Подобные комментарии упрощают проблему и преувеличивают миф, когда правда заключается в том, что потоки никогда не были проще. Есть законные причины для использования тем, и подобные клише мне кажутся контрпродуктивными.