std::thread
Если поток не завершил работу, не вызваны методы join()
или detach()
, но его деструктор thread
уже запущен, то программа аварийно завершится вызовом std::terminate
.
После успешного вызова на потоке методов join()
или detach()
метод joinable()
будет возвращать ложь.
Вызов метода join()
на одном и том же объекте thread
из разных потоков - это undefined behavior, в том числе потому что нельзя делать join()
потоку, который возвращает joinable()
== false
. Это приводит к генерации исключения.
Если вам действительно нужно дождаться выполнения потока из разных потоков, то можно это делать более чистыми способами.
Что случится с detached
-потоками, когда программа выйдет из main?
Их исполнение будет приостановлено ОС, память освобождена (но не через деструкторы, а просто). Необходимо сделить за тем, что происходит в отсоединенных потоках, чтобы после завершения программы файлы не оставались полузаписанными и shared-память не становилась поломанной. Ресурсы наподобие блокировок на файл будут освобождены самой ОС.
std::conditional_variable
Важно знать, что conditional_variable
иногда может просыпаться и без вызова .notify_one()
, поэтому более безопасный код будет выглядеть так:
bool signaled = false;
// start background threads...
// someone will set signaled as true, then call cv.notify_one()
{
std::unique_lock<std::mutex> lock(mutex);
while (!signaled) {
cv.wait(lock);
}
signaled = false;
}
False-sharing
Существует два типа разделения кэш-линий: true sharing и false sharing.
True sharing - это когда потоки имеют доступ к одному и тому же объекту памяти, например, общей переменной или примитиву синхронизации.
False sharing - это доступ к разным данным, но по каким-то причинам оказавшимся в одной кэш-линии процессора.
И тот, и другой случай вредит производительности из-за необходимости аппаратной синхронизации кэш-памяти процессора, однако если первый случай часто неизбежен, то второй можно и нужно исключать.
В случае постоянной модификации данных в условиях false sharing, процессору в соответствии с протоколом когерентности кэша необходимо инвалидировать эту кэш-линию целиком для остальных ядер процессора.
Другой поток уже не сможет пользоваться своими данными, несмотря на то, что они уже лежат в L1 кэше его ядра. Вследствие этого между ядрами происходит синхронизация памяти. Данная операция дорого обходится, если потоки выполняют что-то в цикле - производительность может падать в разы.
На архитектуре x86 в кэш-линию может помещаться 64 байта данных, поэтому если работа происходит с массивом структур данных в многопоточке, то нужно позаботиться о следующих вещах:
- Выравнивание массива
- Наличие подкладки до 64 байт (
padding
)
Заимствования:
c++ - When should I use std::thread::detach? - Stack Overflow
Делиться не всегда полезно: оптимизируем работу с кэш-памятью / Хабр (habr.com)