LINUX.ORG.RU

Утечка памяти после detached потока

 ,


0

1

Здравствуйте.

Столкнулся с такой вот проблемой. Необходимо запускать однотипные потоки, следить за ними не надо - сделали своё дело - и пока. Для этого, как известно, используются потоки с атрибутом detached. Однако, когда я создаю такие потоки - после завершения каждого из них - стек не очищается, остается выделенным 1024k в моём случае (Centos 6.6). Даже после уничтожения 1-го потока он прибавляется на 65404к.

Всё что нашёл по этому поводу как раз связано с тем, что используются не detached потоки, либо используются для создания потоков vector и пишется, что якобы это связано с ними.

Но я упростил пример, всё просто - и всё равно память утекает.

Что можете подсказать?


#include <iostream>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/unistd.h>


using namespace std;

int ThreadToClose;

void * ThreadFunc(void * myid)
{
	int * id = (int *) myid;
	cout<<"thread "<<*id<<" started"<<endl;
	while(true)
	{
		if(ThreadToClose == *id) break;
	}
	cout<<"thread "<<*id<<" stopped"<<endl;
	pthread_exit(NULL);
}


int main(int argc,char*argv[])
{

pthread_t _thread1;
pthread_t _thread2;
pthread_t _thread3;

int * id1 = new int(1);
int * id2 = new int(2);
int * id3 = new int(3);

pthread_create(&_thread1, NULL, ThreadFunc, id1);
pthread_detach(_thread1);
sleep(1);

pthread_create(&_thread2, NULL, ThreadFunc, id2);
pthread_detach(_thread2);
sleep(1);

pthread_create(&_thread3, NULL, ThreadFunc, id3);
pthread_detach(_thread3);
sleep(1);

int ThreadsCount = 1;

while(1)
{
	char c;
	cin>>c;
	
	if (c == 'c') 
	{
		ThreadToClose = ThreadsCount++;
	}

	if (ThreadToClose == 4) break;
}
    exit(0);
}
antp
() автор топика

у меня волгринд только на не освобожденные int ругается

anonymous
()
Ответ на: комментарий от antp

id1, id2, id3 = new int(1,2,3); А где delete [] ?

А, и вообще давай дальше эксперементируй, тебе понравится когда узнаешь, что система умнее тебя

Dennis7
()
Ответ на: комментарий от antp

Со временем да. mm поймет, что данные страницы памяти принадлежат уже несуществущему процессу, и вернет их в общую кучу, но у тебя тут налицо программерская ошибка в коде. А что если это будет вызываться 100500 раз в секунду, как на сервере? Так писать нельзя, и OOM-killer может и не справится.

Dennis7
()
Ответ на: комментарий от antp

ну дык выкладывай цифры, и показывай пальцем что именно тебя волнует...

особенно в разрезе «Даже после уничтожения 1-го потока»

anonymous
()
Ответ на: комментарий от Dennis7

это просто тестовый пример. в реальном коде не так. и, кстати, если расширить и создавать процессы во время исполнения, то утечка будет только такая: к примеру, создаем 3 потока, удаляем один, память под 3 осталась. при создании еще одного, память новая не выделяется, используется от удаленного и т.д. это может быть и не сильно критично, но мне может быть не нужно оставлять эту память, возникшую при каких-то пиковых нагрузках.

а насчет того, что mm когда-то удалит - хорошо, создам и подожду, возможно ты и прав.

antp
() автор топика
Ответ на: комментарий от anonymous

конкретно не в количестве дело, а в качестве. можно, конечно, и память под стек меньшую выделять. но меня волнует то, что после создания потока deatached и его завершения память, выделенная для него в основном процессе, остается, а что это как не утечка памяти?

antp
() автор топика
Ответ на: комментарий от antp

память, выделенная из кучи, доступна всем тредам процесса и освобождается только либо явно либо при завершении всего процесса

/thread

anonymous
()
Ответ на: комментарий от antp

Знаешь что такое адресное пространство процесса? И как процесс получает доступ к страницам памяти, и как он запрашивает память, если ему ее не хватает? И где и как хранятся локальные и глобальные переменные?

Блин... да ну тебя. Изучи ассемблер, тогда поймешь что и как, и куда и зачем.

Dennis7
()

valgrind --leak-check=full:

==13306== 
==13306== HEAP SUMMARY:
==13306==     in use at exit: 72,716 bytes in 4 blocks
==13306==   total heap usage: 8 allocs, 4 frees, 73,636 bytes allocated
==13306== 
==13306== LEAK SUMMARY:
==13306==    definitely lost: 0 bytes in 0 blocks
==13306==    indirectly lost: 0 bytes in 0 blocks
==13306==      possibly lost: 0 bytes in 0 blocks
==13306==    still reachable: 72,716 bytes in 4 blocks
==13306==         suppressed: 0 bytes in 0 blocks
Утечек не вижу.

gag ★★★★★
()
Ответ на: комментарий от anonymous

как получить тогда к ней доступ чтобы явно её освободить? ты хочешь сказать, что вот эти id - они хранятся в области кучи, отведенной под конкретный дочерний поток? выделены-то они были в основном потоке и до создания всяких дочерних. а в дочерних никаких выделений памяти под глобальные переменные, как видишь, нету.

antp
() автор топика
Ответ на: комментарий от antp

Если добавишь

delete id1;
delete id2;
delete id3;
перед exit(0);, картинка улучшится:
==13348== HEAP SUMMARY:
==13348==     in use at exit: 72,704 bytes in 1 blocks
==13348==   total heap usage: 8 allocs, 7 frees, 73,636 bytes allocated
==13348== 
==13348== 72,704 bytes in 1 blocks are still reachable in loss record 1 of 1
==13348==    at 0x4C29C0F: malloc (vg_replace_malloc.c:299)
==13348==    by 0x4EBFE1F: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==13348==    by 0x400F2D9: call_init.part.0 (in /lib/x86_64-linux-gnu/ld-2.22.so)
==13348==    by 0x400F3EA: _dl_init (in /lib/x86_64-linux-gnu/ld-2.22.so)
==13348==    by 0x4000CC9: ??? (in /lib/x86_64-linux-gnu/ld-2.22.so)
==13348== 
==13348== LEAK SUMMARY:
==13348==    definitely lost: 0 bytes in 0 blocks
==13348==    indirectly lost: 0 bytes in 0 blocks
==13348==      possibly lost: 0 bytes in 0 blocks
==13348==    still reachable: 72,704 bytes in 1 blocks
==13348==         suppressed: 0 bytes in 0 blocks
Остался только один неосвобождённый блок, на который мы влиять не можем, а 12 потерянных байт от 3-х int ушли.

gag ★★★★★
()
Ответ на: комментарий от Dennis7

вот какая разница знаю я или нет? вопрос не в этом, а в поведении потоков. я использую документацию на pthreads и С, пример, как ты видишь - простой до нельзя. или мне что - в коде на C писать на ассемблере? или сначала свой компилятор чтобы с кучей там работал и проч.

да, valgrind её не показывает.

ок, давайте ещё проще. вот есть сервер, он принимает соединения, выделяет на каждое соединение свой поток. а если соединение закрывается - его надо удалить поток и всё. мне не надо чтобы они копились - они могут непредсказуемо возникать.

antp
() автор топика
Ответ на: комментарий от antp

ок, давайте ещё проще. вот есть сервер, он принимает соединения, выделяет на каждое соединение свой поток. а если соединение закрывается - его надо удалить поток и всё

Создавать поток на соединение можно только в учебных целях, на практике так делать нерационально. Вместо обслуживания клиентов система будет занята сама собой: созданием и удалением потоков. В go создаются coroutines, вот их можно плодить, так как они используют общий системный поток.

gag ★★★★★
()
Ответ на: комментарий от antp

Да дело не в этом, плох твой код или хорош(он конечно плох), а в том, что если ты взял память под что-то, то верни ее, если она тебе не нужна. А еще скажи об этом системе всякими madvise(...) , так будет лучше дать понять, как ты собираешься эту память использовать.

Dennis7
()
Ответ на: комментарий от antp

и все равно

valgrind ничего не показывает, значит всё как было в порядке.

Если не веришь, сделай бенч: создай, а потом удали очень много потоков.

gag ★★★★★
()
Ответ на: комментарий от antp

тебе уже написали — сделай delete на каждый свой new; так ты освободишь все, что аллоцировал сам, а все что аллоцировалось неявно так же неявно и освободится (если нет баги в библиотеке :) и все будет хорошо; здесь нет никаких проблем

anonymous
()
Ответ на: комментарий от Dennis7

давай ещё раз: это пример. в нем использовано только то, что я нашел в инете на предмет того, как использовать pthreads, что конкретно там еще надо освободить? что такое создаётся по-твоему в процедуре потока, что мешает удалению?

antp
() автор топика
Ответ на: комментарий от anonymous

сделал уже - написал выше и не работает. ладно, всем спасибо, знали бы - сказали.

antp
() автор топика
Ответ на: комментарий от gag

На чем тестрируешь? х86-64? А давай-ка на MIPS роутере, где каждый Кб памяти нужен и система должна работать 24/7. Закоммить мне такой код в основую прошивку или нет,А?

Dennis7
()
Ответ на: комментарий от Dennis7

чувак, спасибо, иди покури. ты реально думаешь что в этом примере мой настоящий код? я не хочу в твою команду, честное слово. мне нужен ответ на вопрос, мнение профессионалов. чтобы показали конкретно что не так, ведь в примере сделано практически так, как показано на всех примерах в инете.

antp
() автор топика
Ответ на: комментарий от antp

Чувак, уже покурил. А раз ты не показываешь твой настоящий код, то значит он такой же говённый, как та «лапша», что ты явил миру профессионалов.

Dennis7
()
Ответ на: комментарий от antp

ведь в примере сделано практически так, как показано на всех примерах в инете.

int * id1 = new int(1);
int * id2 = new int(2);
int * id3 = new int(3);

Не те примеры вы смотрите.

Примеры, в которых рекомендуют заводить по потоку на запрос, так же смотреть не следует.

eao197 ★★★★★
()
Ответ на: комментарий от antp

короче если адекватные люди тут ещё есть, то я сделал вообще всё проще:

#include <iostream>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/unistd.h>


using namespace std;

void * ThreadFunc(void *)
{
	sleep(10);
	pthread_exit(NULL);
}


int main(int argc,char*argv[])
{
	pthread_t _thread1;
	pthread_t _thread2;
	pthread_t _thread3;

	pthread_create(&_thread1, NULL, ThreadFunc, NULL);
	pthread_detach(_thread1);

	pthread_create(&_thread2, NULL, ThreadFunc, NULL);
	pthread_detach(_thread2);

	pthread_create(&_thread3, NULL, ThreadFunc, NULL);
	pthread_detach(_thread3);

	char c;
	cin>>c;
	
	exit(0);
}

никаких явно выделенных переменных вообще нет. через 10 секунд потоки завершаются (и это видно в pstree), а в pmem - память [ anon ] остаётся, выделенная пот них.

может быть действительно какой-то баг в библиотеке?

antp
() автор топика
Ответ на: комментарий от eao197

Отправной точкой послужила книга Шона Уолтона «Создание сетевых приложений в среде Linux». А вы что рекомендуете почитать?

antp
() автор топика
Ответ на: комментарий от antp

а в pmem - память [ anon ] остаётся, выделенная пот них.

Это в книге написано, что под них? Или всё же догадка? Оно выглядит, что под них. Но как я уже предлагал, если стартовать 1000 потоков, а потом завершить их, будет видно, что память освободится. Но не 100%. Если стартовать ещё раз 1000 и завершить, то потребление вернётся к тому же уровню. И вот только если много раз повторять: стартовать кучу потоков, остановить, померять потребление,.. кол-во потребляемой памяти будет постоянно(!) расти и расти, тогда утечка. Но если оно будет постоянно возвращаться к приблизительно(!) одному уровню, тогда утечки нет, просто это скрытые от пользователя системные ресурсы для менеджмента потоков.

gag ★★★★★
()
Ответ на: комментарий от Dennis7

На чем тестрируешь? х86-64?

Да.

Речь шла об утечках, а не одноразовом выделении ресурсов в течение всей жизни процесса. Так что не понял как вопрос

где каждый Кб памяти нужен и система должна работать 24/7

относится к делу.

gag ★★★★★
()
Ответ на: комментарий от gag

этот пример можно переписать и стартовать потоки по-очереди и тогда видно как в pmem создаётся каждый раз при создании потока 1024к (в моём случае). а если удалить поток, то она останется, а если создать после этого еще один, то не прибавится (видимо используется от удаленного).

но, в общем, я понял, похоже это так менеджер памяти так работает.

большое спасибо, gag, буду тестировать в реальном примере с большим количеством потоков на длительном промежутке времени.

antp
() автор топика
Ответ на: комментарий от antp

в pmem создаётся каждый раз при создании потока 1024к (в моём случае). а если удалить поток, то она останется

это просто область в виртуальном адресном пространстве процесса, реальные страницы памяти под нее не выделены (точнее возвращены в систему после завершения потока): смотри параметр RSS (ключ -x)

ты бы еще адреса сложил и стал ныть про утекающую память
блин, наберут по объявлению...

anonymous
()
Ответ на: комментарий от anonymous

я не понял про RSS - он что, должен стать равен 0? но он не меняется. каким методом тогда узнать что память возвращена? потому что в документации так написано?

antp
() автор топика
Ответ на: комментарий от antp

т.е. тебе несколько килобайт все равно покоя не дают? ты хочешь все до капли выжать? ну извини, библиотеку написали исходя из того, что лучше в этом случае оставить выделенными несколько страниц памяти, зато новый тред быстрее запустится, чем освобождать всю память

ты объясни, что за проблема то у тебя? сколько у тебя памяти всего, и сколько в твоем use case'е оказывается выделенной процессу, который ее по факту не использует, что ты это так подробно расследуешь?

anonymous
()
Ответ на: комментарий от antp

а в pmem - память [ anon ] остаётся, выделенная пот них.

Все правильно. Если ты сразу создаешь 100500 потоков, то в линуксе память из процесса при нормальных обстоятельствах НЕ возвращается в ОС. Это так называемое оптимистичное выделение памяти. Однако, ОС в случае нехватки ОЗУ под задачи может забрать всю память, которую процессы пометили как освобожденную и тогда значения выделенной памяти на процесс изменятся [в меньшую сторону].

Чтобы контролировать расход памяти тебе нужно так или иначе вызывать pthread_join(), либо вводить счетчики запущенных потоков (например, через семафоры), либо ограничить общее кол-во запускаемых потоков программой в выделенный промежуток времени. Вообще, рекомендую почитать книжек (можно, Стивенса) и продумать архитектуру приложения. 100500 одновременных потоков не сделают программу быстрее, чем может позволить аппаратура, а вот запуск сколько нужно потоков с раскидыванием по ядрам это другое дело. Да, и еще вопрос в том, что внутри потоков: числодробилка или что-то прерываемое (сокеты)?... Ну, ты понял :)

anonymous
()
Ответ на: комментарий от anonymous

Спасибо за разъяснение. Да, там сокета и работа по сети. В общем, я понял - утечки нет. Это особенность выделения памяти. А насчёт архитектуры подумаю и почитаю материалы.

antp
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.