Что нужно знать при погружении в многопоточное программирование на C++
Я сейчас разрабатываю приложение для беспроводных сетей на C++, и встал перед задачей, когда мне нужно будет многопоточно организовать работу программы в одном процессе, а не иметь их в отдельных процессах. В теории я понимаю, что такое многопоточность, но пока не погружался в практическую реализацию.
Что должен знать каждый программист при написании многопоточного кода на C++?
5 ответ(ов)
Я бы сосредоточился на том, чтобы проектировать систему как можно более разделённой, чтобы минимизировать количество общих ресурсов между потоками. Если вы убедитесь, что у вас нет статических переменных и других ресурсов, общих между потоками (кроме тех, которые вы бы использовали, если бы проектировали систему с процессами вместо потоков), то у вас всё будет в порядке.
Поэтому, хотя вам действительно нужно учитывать такие концепции, как блокировки, семафоры и т. д., лучший способ справиться с задачей — это стараться избегать их.
Вам стоит ознакомиться с такими понятиями, как блокировки, мьютексы, семафоры и условные переменные.
Одним советом: если ваше приложение имеет пользовательский интерфейс, убедитесь, что все изменения в нем выполняются из потока UI. Большинство фреймворков и инструментов для работы с UI могут приводить к сбоям или неожиданному поведению, если с ними взаимодействуют из фонового потока. Обычно они предоставляют какой-либо метод для диспетчеризации, который позволяет выполнить функцию в потоке UI.
Не предполагайте, что внешние API являются потокобезопасными. Если это прямо не указано в их документации, не вызывайте их одновременно из нескольких потоков. Вместо этого ограничьте ваше использование одного API одним потоком или используйте мьютекс для предотвращения одновременных вызовов (это довольно схоже с ранее упомянутыми библиотеками GUI).
Следующий момент касается языка. Помните, что C++ (в данный момент) не имеет четко определенного подхода к потокам. Компилятор/оптимизатор не знает, может ли код быть вызван одновременно. Ключевое слово volatile
полезно для предотвращения определенных оптимизаций (например, кэширования полей памяти в регистрах ЦП) в многопоточных контекстах, но это не механизм синхронизации.
Я бы рекомендовал использовать Boost для примитивов синхронизации. Не связывайтесь с API платформы. Они усложняют портирование вашего кода, поскольку обеспечивают аналогичную функциональность на всех основных платформах, но с немного отличающимся поведением в деталях. Boost решает эти проблемы, предоставляя пользователю только общую функциональность.
Кроме того, если существует хоть малейшая возможность того, что структура данных может быть изменена двумя потоками одновременно, используйте примитивы синхронизации для её защиты. Даже если вы думаете, что это произойдет только раз в миллион лет.
Одно из полезных решений, которое я нашел, заключается в том, чтобы сделать приложение настраиваемым в отношении реального количества потоков, используемых для различных задач. Например, если у вас несколько потоков, обращающихся к базе данных, сделайте количество этих потоков настраиваемым через параметр командной строки. Это невероятно удобно при отладке: вы можете исключить проблемы, связанные с потоками, установив значение равным 1, или наоборот, проверить их, установив высокое значение. Это также помогает определить оптимальное количество потоков для вашей задачи.
Обязательно протестируйте свой код как на одноядерной системе, так и на многоядерной.
Согласно комментариям, обратите внимание на следующие конфигурации:
- Один сокет, одно ядро
- Один сокет, два ядра
- Один сокет, более двух ядер
- Два сокета, по одному ядру в каждом
- Два сокета, комбинация одноядерных, двухъядерных и многоядерных процессоров
- Несколько сокетов, комбинация одноядерных, двухъядерных и многоядерных процессоров
Основным ограничивающим фактором в данном случае будет стоимость. В идеале сосредоточьтесь на тех типах систем, на которых будет работать ваш код.
Программно определить количество ядер на машине
Безопасно ли делать fork из потока?
Что такое процесс и поток?
Что такое бес блокировочное многопоточное программирование?
Как удалить элемент из std::vector<> по индексу?