Good day everyone!
I would like to discuss the following problem: a program creates a thread in the program using the pthread library, the OS allocates some amount of memory to the stack for it, etc. After this thread has completed and terminated, pthread_join () is called from another thread, which takes the return code. At the same time, it is not noticeable that the memory allocated for this thread is freed (when calling pthread_create (), about 100 KB were allocated, from which nothing was released either after return () or after pthread_join () ). Can anyone explain why? Am I doing something wrong, or could it be due to the behavior of the OS?

  • one
    Optionally, the memory allocated and freed is actually returned to the system. This is an optimization property. With a secondary request for memory, it can be allocated from the pool, which is much faster. By the way, the same story with malloc / free. - skegg
  • The fact is, if I create a second one after I destroy the first stream, the amount of allocated memory grows again by 100 Kb, while creating the next one, another 100 plus, etc. - margosh
  • Something tells me that you should not worry about it very much. - skegg
  • Do you have a suitable link to something that tells you so? - margosh
  • one

3 answers 3

@margosh , I also (jointly with @mikillskegg and almost intuitively) believe that the memory that the stream took is reused. I will give an illustration right here.

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> #include <sys/select.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <sys/wait.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/resource.h> #include <pthread.h> #include <semaphore.h> #define fatal(msg) ({perror(msg); exit(-1);}) pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int nth = 0; void * thcli (void *a) { pthread_mutex_lock(&lock); nth--; pthread_mutex_unlock(&lock); return (void *)1; } int main (int ac, char *av[]) { int i, n = av[1]? atoi(av[1]):100; if (n < 1) n = 100; pthread_t th[n]; void *res[n]; char buf[100]; do { pthread_mutex_lock(&lock); for (i = 0; i < n; i++) { if (pthread_create (&th[i], NULL, thcli, NULL)) fatal("create"); nth++; } printf ("run %d nth = %d\n",i,nth); pthread_mutex_unlock(&lock); for (i = 0; i < n; i++) { if (pthread_join (th[i], &res[i])) fatal("join"); } printf ("join %d nth = %d\nAgain ?\n",i,nth); } while (fgets(buf,sizeof(buf),stdin), buf[0] == 'y'); exit (puts("Bye") == EOF); } 

Do not worry about the abundance of inclusions, most of them are needed, just copied for this test from another program.

And here is the illustration

 avp@avp-xub11:~/src/ig/tst$ gcc th.c -pthread ..... avp@avp-xub11:~/src/ig/tst$ ./a.out 381 create: Cannot allocate memory avp@avp-xub11:~/src/ig/tst$ ./a.out 380 run 380 nth = 380 join 380 nth = 0 Again ? y run 380 nth = 380 join 380 nth = 0 Again ? y run 380 nth = 380 join 380 nth = 0 Again ? y run 380 nth = 380 join 380 nth = 0 Again ? . Bye avp@avp-xub11:~/src/ig/tst$ ./a.out 380 run 380 nth = 380 join 380 nth = 0 Again ? y run 380 nth = 380 join 380 nth = 0 Again ? . Bye avp@avp-xub11:~/src/ig/tst$ ./a.out 381 create: Cannot allocate memory avp@avp-xub11:~/src/ig/tst$ 

IMHO it is clear that if the resource is almost enough, then after the completion of the threads, it is again released and reused (at least in the same situation).

UPD-1

@margosh , I'm not growing in Linux. Added feature

 // returns second field for last line selected by 'what' static int pri_mem (int pid, char **what) { char path[1000]; int res = 0; sprintf (path,"/proc/%d/status",pid); FILE *in = fopen(path,"r"); if (!in) { perror(path); return; } while (fgets(path,1000,in)) { char **w = what; while (*w) { if (strncasecmp(path,*w,strlen(*w)) == 0) { fputs(path,stdout); char dummy[1000]; sscanf(path,"%s %d",dummy,&res); break; } w++; } } fclose(in); return res; } 

and slightly changed main (), now prints the memory before each cycle

 avp@avp-xub11:~/src/ig/tst$ gcc th.c -pthread avp@avp-xub11:~/src/ig/tst$ ./a.out 380 VmPeak: 2252 kB VmSize: 2252 kB VmHWM: 312 kB VmRSS: 312 kB VmStk: 136 kB run 380 nth = 380 loop 0: join 380 nth = 0 Exit ? VmPeak: 3116732 kB VmSize: 35044 kB VmHWM: 2180 kB VmRSS: 680 kB VmStk: 136 kB run 380 nth = 380 loop 1: join 380 nth = 0 Exit ? VmPeak: 3116736 kB VmSize: 35044 kB VmHWM: 2192 kB VmRSS: 692 kB VmStk: 136 kB run 380 nth = 380 loop 2: join 380 nth = 0 Exit ? VmPeak: 3116736 kB VmSize: 35044 kB VmHWM: 2192 kB VmRSS: 692 kB VmStk: 136 kB run 380 nth = 380 loop 3: join 380 nth = 0 Exit ? 

and further

 VmPeak: 3116736 kB VmSize: 35044 kB VmHWM: 2192 kB VmRSS: 692 kB VmStk: 136 kB run 380 nth = 380 loop 29: join 380 nth = 0 Exit ? VmPeak: 3116736 kB VmSize: 35044 kB VmHWM: 2192 kB VmRSS: 692 kB VmStk: 136 kB run 380 nth = 380 loop 30: join 380 nth = 0 Exit ? VmPeak: 3116736 kB VmSize: 35044 kB VmHWM: 2192 kB VmRSS: 692 kB VmStk: 136 kB run 380 nth = 380 loop 31: join 380 nth = 0 Exit ? y Bye avp@avp-xub11:~/src/ig/tst$ 

IMHO is not growing. Let's try with a small number of threads

 avp@avp-xub11:~/src/ig/tst$ ./a.out 30 VmPeak: 2252 kB VmSize: 2252 kB VmHWM: 316 kB VmRSS: 316 kB VmStk: 136 kB run 30 nth = 30 loop 0: join 30 nth = 0 Exit ? VmPeak: 248132 kB VmSize: 35044 kB VmHWM: 728 kB VmRSS: 628 kB VmStk: 136 kB run 30 nth = 30 loop 1: join 30 nth = 0 Exit ? VmPeak: 248136 kB VmSize: 35044 kB VmHWM: 740 kB VmRSS: 640 kB VmStk: 136 kB run 30 nth = 30 loop 2: join 30 nth = 0 Exit ? ....... ....... loop 15: join 30 nth = 0 Exit ? VmPeak: 248136 kB VmSize: 35044 kB VmHWM: 740 kB VmRSS: 640 kB VmStk: 136 kB run 30 nth = 30 loop 16: join 30 nth = 0 Exit ? VmPeak: 248136 kB VmSize: 35044 kB VmHWM: 740 kB VmRSS: 640 kB VmStk: 136 kB run 30 nth = 30 loop 17: join 30 nth = 0 Exit ? y Bye avp@avp-xub11:~/src/ig/tst$ 

Perhaps a problem in FreeBsd.

  • Yes, it is clear. Do you have Ubuntu? Could you repeat the experiment and start TOP at the same time? Will the memory in the RES column increase with the passage of several iterations? - margosh
  • one
    @margosh, looked (but couldn’t grab and copy-paste with the mouse). RES changes (636, 636, ... 1160, 836, 636, 636, 1160) completely in a circle or not, I do not know. - avp
  • here but for some reason I change, and it confuses me a lot. I'll try to run your example, thank you! - margosh
  • @margosh, so it changes with me, it only seems to be cyclical. And you are constantly growing? Maybe somewhere you do not call free (), or do you close FILE * (or something like that)? - avp
  • @avp, tested your primerchik at home, its memory is constantly growing, but by a small amount - 2-4 Kb. I tried to transfer a structure to a stream, allocate a splash for it dynamically before pthread_create () , free it after join, it grows, but not by a constant value, for example, when launching 500 threads, the first time is 4292, the second is 4504, the third is 4588, and so on. d. ie, the memory gain is reduced, but it still remains. - margosh

It is strange that no one said about pthread_detach() . I remember if it does not run the memory allocated for the stream is not released and grows very quickly, which is very noticeable for example in htop. I call this thing after pthread_create ().

 pthread_t restrict; if(pthread_create(&restrict, ...)) return 0; pthread_detach(restrict); return 0; 

"The pthread_join function blocks the execution thread that caused it until the thread has completed with the thread identifier." I do not see any connection with the use of memory. She has nothing to do with it.

  • @mikelsv, after pthread_detach () the pthread_join () call for this thread does not work (and the author in the question just talks about using pthread_join ()). Using attached and detached streams imply slightly different processing algorithms. - avp
  • It’s a pity that you don’t see, I’ll quote from Stevens "The call to the pthread_join () function automatically puts the thread in a separate state that allows you to return the resources of the thread back." In this case, the detail does not suit me, since I want to know the completion code after the end of the stream. - margosh
  • @avp, perhaps, FreeBSD does not manage memory allocation differently from Linux, so far, unfortunately, I haven’t found anything worthwhile on this topic. It is somewhat reassuring that the memory gain gradually decreases, but I would like to find out why there is a general increase in consumption. - margosh
  • Yeah, also an option. Tested an example of @avp, nothing leaks. Maybe not in the case flow? Check for memory leaks in valgrind or look for errors. - mikelsv
  • @margosh, most likely this has long been fixed, but something similar (note _pthread_mutex_destroy) maybe you have. - avp

limit of comments exhausted.
@avp , -D_THREAD_SAFE did not save. It may be necessary to revise the logic of purification, in any case, I will try and with detach for a change, suddenly something will change. Yes, join now only 1 thread does when it receives a sign through pipe (), as you once suggested to me, for which many thanks :)

  • @mikelsv, not a process, but a stream! cleansing the memory - not to interfere with the rest, i.e. so that the main thread accepting connections does not waste time on it and that clients do not idle while waiting. The process in each thread launches a copy of the program, and the thread communicates with it. - margosh
  • @avp, by the way, if you are interested, under FreeBSD you can’t spend the same amount of memory spent on your example, because file / proc / PID / status contains other information, such as: cat / proc / 31668 / status fl_sd 31668 1 31668 31668 - sldr 1352376252,694798 1.131754 3,548299 select 0 0 0,0,5 - explanations to the parameters I didn’t find them, and when you run several iterations of the program, they are changed differently than in the listing, so it is intuitively unclear which of them is responsible for what. - margosh
  • @margosh, linux file descriptions from / proc in man 5 proc . In BSD, I don’t know, but try (if you have one) to view cat / proc / [PID] / statm 1362 71 57 11 0 74 0 size total program size (same as VmSize in / proc / [pid] / status) resident resident set size (same as VmRSS in / proc / [pid] / status) share shared pages (from shared mappings) text text (code) lib library (unused in Linux 2.6) data datasheet (unused in Linux 2.6) IMHO numbers in 4K pages. - avp
  • no, there is no such thing, there is only / proc / PID / status, and it looks exactly the same as I quoted above, and there are no descriptions on proc in mana. - margosh
  • Well, there is no court. Positrite may find something suitable. Maybe getrusage () will do, but in linux it gives little information about memory. - avp