Как известно, в стандартной библиотеке C++ есть умный указатель с подсчётом ссылок std::shared_ptr; при желании можно думать о любом другом указателе с подсчётом ссылок, смысл дальнейшего от этого не изменится.
Как известно, при достижении счётчиком ссылок нуля вызывается deleter для указателя, управляемого shared_ptr-ом. По-умолчанию deleter просто применят оператор delete к указателю.
На что я хочу обратить внимание: работу по вызову деструктора и освобождению памяти делает тот тред и тот код, который сбрасывает счётчик до нуля. Если объект содержит другие shared_ptr в качестве своих полей, то часто освобождение этого объекта вызывает каскад освобождений памяти и приводит к задержкам в выполнении треда. Пример кода, могущий привести к таким каскадным высвобождениям, можно найти, например, тут https://bartoszmilewski.com/2013/11/13/functional-data-structures-in-c-lists/. Там односвязные иммутабельные списки, для предотвращения копирования всего списка при модификации, например, только головы, реализованы с использованием shared_ptr и могут иметь общие хвосты. Короче, с помощью shared_ptr реализуется persistence, я думаю вы знакомы с таким подходом.
Я тут подумал и пришёл к такой идее: завести threadsafe очередь для указателей (точнее, для структур, содержащих указатель + указатель на функцию, знающую что с этим указателем делать, ведь нам придётся стереть типы; но это уже детали) и при создании shared_ptr использовать custom deleter, который при вызове будет просто помещать указатель в очередь. Вызывать же деструкторы и освобождать память будет отдельный поток (или потоки?). Он будет брать очередной указатель из очереди и вызывать для него деструктор и освобождать память. Так мы избавим рабочие потоки от необходимости обслуживать каскады высвобождений памяти.
Я понимаю, что у этого подхода тоже будут performance penalties. Обычно куч всего несколько и при большом числе тредов каждая обслуживает несколько тредов. И если тред-освободитель будет освобождать память, он захватит лок у кучи, в которую могут лезть треды для выделения памяти. Там-то они и будут сталкиваться лбами. Это я понимаю. Но, в отличие от гарантированных длинных задержек, вызванных каскадным высвобождением памяти, тут задержки будут размазаны во времени или будут «распределены» между другими тредами, если они полезут выделять память в момент освобождения; или же, очень вероятно, эти задержки вообще не проявятся, если память выделять не полезут. Надо тестировать под различными нагрузками, заранее трудно сказать.
А что ЛОР про это думает? Дискас.