malloc:arena
多线程应用中,并发调用malloc,malloc的实现中,多线程之间需要锁进行同步吗?
大部分时候是不需要的。每个线程有自己的tcache,有自己的arena,有自己的堆。
所以说,对于多线程的进程来说,多个线程有自己的堆,而不是共用一个堆。
glibc中的malloc管理,为了减小多线程之间并发调用malloc时的同步,引入了arena和tcache。
1arena
NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
arena的个数是有限制的,最大值由宏NARENAS_FROM_NCORES定义,n是cpu个数。可以看到在32位系统上,arena的最大值是cpu个数乘以2,在64位系统上,arena最大值是cpu的个数乘以8。当arena个数小于最大值时,那么每个线程使用自己的arena,当arena个数大于最大值时,那么便会有arena被多个线程共用。
static mstate
_int_new_arena (size_t size)
{mstate a;heap_info *h;char *ptr;unsigned long misalign;h = new_heap (size + (sizeof (*h) + sizeof (*a) + MALLOC_ALIGNMENT),mp_.top_pad);...a = h->ar_ptr = (mstate) (h + 1);malloc_init_state (a);...top (a) = (mchunkptr) ptr;set_head (top (a), (((char *) h + h->size) - ptr) | PREV_INUSE);...__libc_lock_lock (a->mutex);return a;
}
当线程第一次调用malloc,会创建一个arena,通过函数_int_new_arena 实现。
通过new_heap申请一个堆内存,申请的堆内存初始化之后的情况如上图所示:
(1)开头存放struct heap_info结构体
(2)struct heap_info之后存放struct malloc_state,也就是arena
(3)struct heap_info之后是top chunk,堆内存初始化的时候,所有的内存都放到top chunk中。
第一次申请内存的时候,fast bin, small bin, unsorted bin, large bin都是空的,这个时候,就从top trunk中截取一段内存返回给用户,同时移动top trunk的位置。可以看到,随着内存不断的申请,top trunk会被切分成很多小块的内存。内存释放时,也就是用户调用free的时候,内存会根据大小放到不同的bin中。
2tcache
因为arena有最大个数限制,当线程个数大于最大值的时候,就会存在一个arena被多个线程共享的情况,为了减小这种竞争,引入了tcache。
申请内存的判断顺序:tcache --> fast bin --> small bin --> unsorted bin --> top chunk --> 系统调用
3main_arena
主线程申请内存时,从main_arena中申请。main_arena是静态定义的,不需要像子线程那样通过new_heap申请一个新的堆,然后在堆上初始化一个arena。初始的main_arena是内存的,top chunk也没有,所以第一次从哪个main_arena中申请内存时,需要从系统申请内存。
static struct malloc_state main_arena =
{.mutex = _LIBC_LOCK_INITIALIZER,.next = &main_arena,.attached_threads = 1
};
主线程的堆内存,通过sbrk来申请。
(gdb) bt
#0 __GI___sbrk (increment=135168) at ./misc/sbrk.c:37
#1 0x00007ffff78a25c6 in __glibc_morecore (increment=<optimized out>) at ./malloc/morecore.c:29
#2 0x00007ffff78a35f9 in sysmalloc (nb=nb@entry=656, av=av@entry=0x7ffff7a1ac80 <main_arena>) at ./malloc/malloc.c:2727
#3 0x00007ffff78a48dd in _int_malloc (av=av@entry=0x7ffff7a1ac80 <main_arena>, bytes=bytes@entry=640) at ./malloc/malloc.c:4407
#4 0x00007ffff78a49c9 in tcache_init () at ./malloc/malloc.c:3245
#5 0x00007ffff78a51de in tcache_init () at ./malloc/malloc.c:3241
#6 __GI___libc_malloc (bytes=72704) at ./malloc/malloc.c:3306
#7 0x00007ffff7caa93a in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#8 0x00007ffff7fc947e in call_init (l=<optimized out>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe468, env=env@entry=0x7fffffffe478)
at ./elf/dl-init.c:70
#9 0x00007ffff7fc9568 in call_init (env=0x7fffffffe478, argv=0x7fffffffe468, argc=1, l=<optimized out>) at ./elf/dl-init.c:33
#10 _dl_init (main_map=0x7ffff7ffe2e0, argc=1, argv=0x7fffffffe468, env=0x7fffffffe478) at ./elf/dl-init.c:117
#11 0x00007ffff7fe32ca in _dl_start_user () from /lib64/ld-linux-x86-64.so.2
#12 0x0000000000000001 in ?? ()
#13 0x00007fffffffe6d1 in ?? ()
#14 0x0000000000000000 in ?? ()
(gdb)
通过proc中maps文件,可以看到,标记heap的是主线程的堆,标记stack的是主线程的栈。子线程的堆和栈,均是通不过mmap申请的。
557bac31f000-557bac340000 rw-p 00000000 00:00 0 [heap]
7fff2c7e7000-7fff2c808000 rw-p 00000000 00:00 0 [stack]