Aether
|
|
« : 17-10-2017 17:39 » |
|
Доброго вечера.
Хочу поинтересоваться для собственного развития: в Windows есть функция CreateThread(). Как я понял, стек потока создаётся в адресном пространстве процесса, а это означает, что скорее всего происходит выделение под него из кучи. В функции изначально есть параметр задающий размер стека, по умолчанию, выделяется 1 МБ.
Что будет, если в функции будет присутствовать какая-либо рекурсия, которая может превысить заданный размер стека потока?
Второе, эти потоки могут выполняться разными ядрами, или это просто логическое разделение?
Как правильно выделять память внутри потока?
Что посоветуете почитать?
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #1 : 17-10-2017 23:03 » |
|
Стек к куче не относится. Про рост стека поищи guard page, это позволяет выделить под стек минимальный объем памяти с автоматическим ростом до установленного лимита. Попытка превысить лимит, скорее всего, вызовет фатальную для процесса ошибку. Это надо рассматривать конкретную ОС.
Потоки могут исполняться где угодно. Все зависит от планировщика ОС. Потоки процессора изолированы также, как разные ядра, они лишь разделяют вычислительные блоки.
Про динамическую память стоит помнить: ее надо не только выделять, но и освобождать. Политика и ограничения зависят от аллокатора. Хороший стиль: выделять и освобождать в одном потоке. Вообще надо стараться минимизировать совместный доступ потоков к rw данным, это снижает вероятность ошибок. Это мое мнение. Возможно кто-то считает иначе. По мне многопроцессность надежнее многопоточности.
|
|
« Последнее редактирование: 17-10-2017 23:18 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Online
Сообщений: 13
|
|
« Ответ #2 : 18-10-2017 04:27 » |
|
... и ещё неплохо бы избегать рекурсий, особенно потенциально глубоких
|
|
|
Записан
|
|
|
|
Aether
|
|
« Ответ #3 : 18-10-2017 07:04 » |
|
Стек к куче не относится.
Я имел ввиду, что менеджер памяти это как-то должен отработать. Выделить память потоку внутри стека процесса нельзя, значит остаётся выделить кусочек из "кучи" и передать его потоку в качестве стека фиксированного размера. В принципе количество виртуальных адресов обычно много больше физического размера памяти, так что можно и разнести с большим запасом всё так, что оно и не пересечётся в почти 100% случаев. Но это предположение. Вообще надо стараться минимизировать совместный доступ потоков к rw данным, это снижает вероятность ошибок.
Мне интересна связка между одновременным выполнением потоков и работой менеджера памяти. Предположим, что два потока одновременно запросили память - не будет ли здесь конфликта? По идее, должно произойти отложение выделения до возврата в основной процесс, подсчёт заявок и только тогда распределение участков. По мне многопроцессность надежнее многопоточности.
Многопоточность экономит память, и позволяет проще осуществлять обмен данными, исходя из того, что адресное пространство одно. Однако, мне не очень понятен их механизм изнутри. ... и ещё неплохо бы избегать рекурсий, особенно потенциально глубоких
Согласен. Обычно так и делаю, иногда задаюсь вопросом: есть ли инструмент для оценки чужого кода на присутствие рекурсий? То есть, речь идёт о построении дерева функций до компиляции.
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Online
Сообщений: 13
|
|
« Ответ #4 : 18-10-2017 07:25 » |
|
Aether, выделение памяти из кучи в потоках происходит совершенно обычным образом (с точки зрения программиста), если ОС позволяет столько памяти выделить, то это работает всегда правильно ...=new ...;
...
delete ...; проблемы начинаются тогда, когда более одного потока пытается работать с уже выделенной памятью - там требуется синхронизация средствами ОС Насчёт инструментов - наверное, есть что-то для этого. Попробовал сейчас встроенный кутешный Clang - на явную бесконечную рекурсию он не среагировал никак, значит тут он не подходит )
|
|
|
Записан
|
|
|
|
darkelf
Молодой специалист
Offline
|
|
« Ответ #5 : 18-10-2017 08:07 » |
|
Я имел ввиду, что менеджер памяти это как-то должен отработать. Выделить память потоку внутри стека процесса нельзя, значит остаётся выделить кусочек из "кучи" и передать его потоку в качестве стека фиксированного размера. В принципе количество виртуальных адресов обычно много больше физического размера памяти, так что можно и разнести с большим запасом всё так, что оно и не пересечётся в почти 100% случаев. Но это предположение.
в unix-подобных ОС память скорее всего выделят через mmap(). А вообще, наверное, можно и из кучи выделить, по крайней мере есть API pthread_attr_setstack, которое позволяет указать в качестве стека любой участок памяти, правда с некоторыми ограничениями на его размер и выравнивание. Мне интересна связка между одновременным выполнением потоков и работой менеджера памяти. Предположим, что два потока одновременно запросили память - не будет ли здесь конфликта? По идее, должно произойти отложение выделения до возврата в основной процесс, подсчёт заявок и только тогда распределение участков.
а что мешает при вызове функций менеджера памяти захватывать/освобождать мутекс? Я имею в виду не снаружи, а внутри функции malloc()/realloc()/free(). Многопоточность экономит память, и позволяет проще осуществлять обмен данными, исходя из того, что адресное пространство одно. Однако, мне не очень понятен их механизм изнутри.
и эта лёгкость может, в том числе, и упрощать внесение ошибок и организацию "гонок" по данным, т.к. практически все данные оказываются разделяемыми, а не только те, которые нужны. При многопроцессном варианте построения программы разделяемые данные можно вынести в разделяемую память, и это уже будет явно. Сравнительно недавно, для упрощения построения многопоточных программ, придумали ещё такую вещь, как транзакционную память, которая может быть реализована как чисто программно, в компиляторе и/или библиотеке, так и с определённой аппаратной поддержкой на уровне процессора.
|
|
« Последнее редактирование: 18-10-2017 09:50 от darkelf »
|
Записан
|
|
|
|
Aether
|
|
« Ответ #6 : 18-10-2017 09:23 » |
|
а что мешает при вызове функций менеджера памяти захватывать/освобождать мутекс? Я имею в виду не снаружи, а внутри функции malloc()/realloc()/free().
Тоже самое в итоге получится. Поток будет ждать своей очереди, но есть нюансы в плюсах и минусах. Сравнительно недавно, для упрощения построения многопоточных программ, придумали ещё такую вещь, как транзакционную память, которая может быть реализована как чисто программно, в компиляторе и/или библиотеке, так и с определённой аппаратной поддержкой на уровне процессора. Это незнакомый зверь. Как понял, суть в предоставлении буфера для каждого потока, в который можно писать не ожидая разрешения на запись, а читать, стало быть, можно только оттуда, где актуально. Возможно, в каких-то отдельных задачах этот подход оправдан.
|
|
|
Записан
|
|
|
|
darkelf
Молодой специалист
Offline
|
|
« Ответ #7 : 18-10-2017 09:49 » |
|
а что мешает при вызове функций менеджера памяти захватывать/освобождать мутекс? Я имею в виду не снаружи, а внутри функции malloc()/realloc()/free().
Тоже самое в итоге получится. Поток будет ждать своей очереди, но есть нюансы в плюсах и минусах. В общем случае - да, но могут быть варианты. Например, во внутреннем менеджере памяти для каждого потока выделять определённый объём, гораздо больший, чем запрашивают на данный момент, а потом из этой области распределять под мелкие выделения. Тогда, пока есть свободное место в этой области, блокировки не потребуются, они будут необходимы, когда данный объём исчерпается, и будет необходимо выделить новую область. Т.е. получится как-бы внутренняя персональная для каждого потока "куча".
|
|
|
Записан
|
|
|
|
Aether
|
|
« Ответ #8 : 18-10-2017 10:57 » |
|
Да, согласен, резервирование впрок может помочь.
А можно ещё поинтересоваться: стоит ли для ускорения вычислений создавать количество потоков кратное или равное количеству ядер? И новые потоки делят квант процесса или могут образовывать собственные, независимые от процесса, кванты? Иными словами, создавая множество потоков, можно создать ситуацию, когда они начнут "вытеснять" другие процессы, вызывая общее торможение системы, или нет?
|
|
|
Записан
|
|
|
|
darkelf
Молодой специалист
Offline
|
|
« Ответ #9 : 18-10-2017 11:19 » |
|
А можно ещё поинтересоваться: стоит ли для ускорения вычислений создавать количество потоков кратное или равное количеству ядер?
Есть такой подход. В принципе надо создавать столько потоков, сколько надо. Для обеспечения максимальной производительности, например в вычислениях, хорошим вариантом может быть и число потоков равное числу ядер. И новые потоки делят квант процесса или могут образовывать собственные, независимые от процесса, кванты? Иными словами, создавая множество потоков, можно создать ситуацию, когда они начнут "вытеснять" другие процессы, вызывая общее торможение системы, или нет?
Обычно, объектом диспетчеризации ядра ОС являются не процессы, а потоки. Точнее, когда не было многопоточных программ было соответствие поток=процесс. Сейчас это не так. Сейчас процесс - это хранилище ресурсов - распределённая память, открытые файлы и т.д - т.е. как-бы пассивная сущность. А поток (активная сущность) это контекст выполнения, у которого, собственно, кроме состояния регистров и стека ничего и нет. В соответствии с этим все потоки всех программ участвуют в диспетчеризации и программа на каком-нибудь четырёх ядерном процессоре, запустив 4 потока может забрать все ресурсы компьютера. С этим тоже есть возможность бороться - например в том-же unix-е есть setrlimit(), при помощи которого можно контролировать совсем уж слетевшие с катушек процессы. Более мягко это, имхо, разруливается на уровне назначения приоритетов потокам (см. например sched_setscheduller() и прочие функции с префиксом sched_*) - т.е. если эта работа какая-нибудь не сильно важная, но объёмная - можно запустить поток/процесс с пониженным приоритетом. Если есть важная работа - выполняется она, если нет - ядро ОС ставит на выполнение этот процесс/поток.
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #10 : 19-10-2017 07:58 » |
|
Кстати, в glibc используется блокировка в malloc. На ней легко сломаться, если из обработчика сигнала сделать malloc.
Оптимальное количество потоков сильно зависит от решаемых задач и предназначения хоста. На десктопе легко могут быть сотни-тысячи потоков и слабая загрузка его 2-4 ядер. На сервере может быть частичная или полная загрузка всех его десятков ядер незначительно превосходящим числом потоков. Если рассчитываешь, что потоки будут блокироваться на IO, можно увеличить их число. К примеру, для MySQL рекомендована настройка N+2. Для web-сервера можно взять N*2.
|
|
« Последнее редактирование: 19-10-2017 08:11 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
Aether
|
|
« Ответ #11 : 19-10-2017 16:27 » |
|
Кстати, в glibc используется блокировка в malloc. На ней легко сломаться, если из обработчика сигнала сделать malloc.
Из обработчика сигнала? Оптимальное количество потоков сильно зависит от решаемых задач и предназначения хоста.
Задача по типу рендеринга: есть небольшое число исходных данных, и по ним нужно рассчитать группу матриц на разные случаи их комбинаций.
|
|
|
Записан
|
|
|
|
RXL
Технический
Администратор
Offline
Пол:
|
|
« Ответ #12 : 19-10-2017 21:58 » |
|
Вызов malloc ставит блокировку, обработчик сигнала прерывает malloc, тоже вызывает malloc.
Для числодробилок оптимально число потоков по числу аппаратных ядер. Если есть HT и памяти достаточно, можно по числу аппаратных потоков. Если велика нагрузка на память, число потоков нужно снижать или рассмотреть, почему они друг другу мешают. Но это уже другая тема.
|
|
« Последнее редактирование: 19-10-2017 22:03 от RXL »
|
Записан
|
... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
|
|
|
|