Linux C++ - pthread_mutexattr_destroy가 없다면 메모리 누수가 발생할까요?
"리눅스 API의 모든 것, 기초 리눅스 API" 책을 보면,
Listing 30-3: Setting the mutex type
; https://broman.dev/download/The%20Linux%20Programming%20Interface.pdf#page=685
attr에 대해 init/destroy를 호출하는 코드가 나옵니다.
#include <cstdio>
#include <pthread.h>
int main()
{
pthread_mutex_t cs;
pthread_mutexattr_t mutex_attr;
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&cs, &mutex_attr);
pthread_mutexattr_destroy(&mutex_attr);
pthread_mutex_destroy(&cs);
return 0;
}
그렇다면, pthread_mutexattr_destroy를 하지 않는 경우 메모리 누수가 발생할까요? 이를 위해 잠시 소스코드를 찾아봤는데, 제가 테스트한 glibc의 경우,
$ cat /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h
...[생략]...
#define __SIZEOF_PTHREAD_MUTEXATTR_T 4
...[생략]...
typedef union
{
char __size[__SIZEOF_PTHREAD_MUTEXATTR_T];
int __align;
} pthread_mutexattr_t;
...[생략]...
pthread_mutexattr_t는 고정 크기의 구조체로 정의돼 있었습니다. 그러니까, 구현 코드를 봐도,
$ cat /home/kevin/glibc/glibc-2.31/nptl/pthread_mutexattr_init.c
int
__pthread_mutexattr_init (pthread_mutexattr_t *attr)
{
// ...[생략]...
if (sizeof (struct pthread_mutexattr) != sizeof (pthread_mutexattr_t))
memset (attr, '\0', sizeof (*attr));
// ...[생략]...
((struct pthread_mutexattr *) attr)->mutexkind = PTHREAD_MUTEX_NORMAL;
return 0;
}
$ cat /home/kevin/glibc/glibc-2.31/nptl/pthread_mutexattr_destroy.c
// ...[생략]...
int
__pthread_mutexattr_destroy (pthread_mutexattr_t *attr)
{
return 0;
}
딱히 내부에 할당 코드는 없습니다. 어떠한 alloc/free도 동반하지 않기 때문에 메모리 누수가 발생하지 않습니다.
단지, 이게 버전마다, 혹은 구현체마다 다를 수 있다는 것에는 주의를 해야 하는데요, 실제로, 아래의 github 코드를 보면,
pthread_mutexattr_init.c
; https://github.com/BrianGladman/pthreads/blob/master/pthread_mutexattr_init.c
pthread_mutexattr_destroy.c
; https://github.com/BrianGladman/pthreads/blob/master/pthread_mutexattr_destroy.c
윈도우 운영체제로 포팅한 소스코드라고 하는데 저 코드에서는 calloc/free의 쌍으로 다루기 때문에 메모리 누수가 발생하게 됩니다. 그러니까, 이식성 있는 코드를 만든다면 init/destroy를 쌍으로 호출하는 것을 권장한다는 정도가 되겠습니다.
mutex 자체의 경우도 process 내부의 동기화 용도로 쓰는 경우라면 pthread_mutex_destroy를 없애도 메모리 누수가 없습니다. 설령 프로세스 간 동기화(
PTHREAD_PROCESS_SHARED)를 한다고 해도 결국 조건 변수를 공유해서 사용하는 것이므로 mutex 자체에는 메모리 할당과 관련이 없을 듯합니다. 게다가 mutex 자체를 끊임없이 생성/삭제를 하는 경우도 드물기 때문에 저 코드에 메모리 누수가 있어도 현실적으로 발견하기는 쉽지 않을 것입니다.
다음은 간단하게 mutex 관련 메모리 누수를 테스트한 코드입니다.
#include <cstdio>
#include <unistd.h>
#include <ios>
#include <iostream>
#include <fstream>
#include <string>
#include <pthread.h>
// https://stackoverflow.com/questions/669438/how-to-get-memory-usage-at-runtime-using-c
void process_mem_usage(double& vm_usage, double& resident_set)
{
using std::ios_base;
using std::ifstream;
using std::string;
vm_usage = 0.0;
resident_set = 0.0;
// 'file' stat seems to give the most reliable results
//
ifstream stat_stream("/proc/self/stat", ios_base::in);
// dummy vars for leading entries in stat that we don't care about
//
string pid, comm, state, ppid, pgrp, session, tty_nr;
string tpgid, flags, minflt, cminflt, majflt, cmajflt;
string utime, stime, cutime, cstime, priority, nice;
string O, itrealvalue, starttime;
// the two fields we want
//
unsigned long vsize;
long rss;
stat_stream >> pid >> comm >> state >> ppid >> pgrp >> session >> tty_nr
>> tpgid >> flags >> minflt >> cminflt >> majflt >> cmajflt
>> utime >> stime >> cutime >> cstime >> priority >> nice
>> O >> itrealvalue >> starttime >> vsize >> rss; // don't care about the rest
stat_stream.close();
long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // in case x86-64 is configured to use 2MB pages
vm_usage = vsize / 1024.0;
resident_set = rss * page_size_kb;
}
#define NANO_PER_SEC ((__clock_t) 1000000000)
#define NANO_PER_MILLI ((__clock_t) 1000000)
#define MILLI_PER_SEC ((__clock_t) 1000)
timespec diff(timespec start, timespec end)
{
timespec temp;
if ((end.tv_nsec - start.tv_nsec) < 0) {
temp.tv_sec = end.tv_sec - start.tv_sec - 1;
temp.tv_nsec = NANO_PER_SEC + end.tv_nsec - start.tv_nsec;
}
else {
temp.tv_sec = end.tv_sec - start.tv_sec;
temp.tv_nsec = end.tv_nsec - start.tv_nsec;
}
return temp;
}
clock_t gettotalmillisec(const timespec& time)
{
return time.tv_sec * MILLI_PER_SEC + time.tv_nsec / NANO_PER_MILLI;
}
int main()
{
pthread_mutex_t cs;
struct timespec tspec1, tspec2;
clock_gettime(CLOCK_REALTIME_COARSE, &tspec1);
while (true)
{
pthread_mutexattr_t mutex_attr;
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&cs, &mutex_attr);
// pthread_mutexattr_destroy(&mutex_attr);
// pthread_mutex_destroy(&cs);
clock_gettime(CLOCK_REALTIME_COARSE, &tspec2);
timespec elapsed = diff(tspec1, tspec2);
clock_t timeDiff = gettotalmillisec(elapsed);8
if (timeDiff >= 1000) // https://www.sysnet.pe.kr/2/0/11914
{
tspec1 = tspec2;
double vm, rss;
process_mem_usage(vm, rss);
printf("VM: %.2f; RSS: %.2f\n", vm, rss);
}
}
return 0;
}
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]