为什么多个线程在持有互斥体时使用过多内存

Gur*_*ngh 6 rust

下面的代码在单线程中使用约 150MB,但在 100 个线程中使用几个 GB:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let f = Arc::new(Mutex::new(Foo::new("hello")));

    let mut threads = vec![];
    for i in 0..100 {
        let f = f.clone();
        let t = thread::spawn(move || loop {
            let mut locked = f.lock().unwrap();
            *locked = Foo::new("hello");
            drop(locked);
            println!("{} reloaded", i);
            thread::yield_now();
        });
        threads.push(t);
    }

    threads.into_iter().for_each(|h| h.join().unwrap());
}

pub struct Foo {
    _data: Vec<String>,
}

impl Foo {
    fn new(s: &str) -> Foo {
        Foo {
            _data: vec![s.to_owned(); 1024 * 1024],
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当持有 时LockGuard,线程应该具有独占访问权限。因此,Foo此时应分配新值并删除旧值。因此,从多个线程调用时使用这么多内存对我来说没有任何意义。

谁能解释一下为什么这段代码使用这么多内存?

即使有 1000 个线程,Java 中的类似代码也能保持约 200mb 的内存。

import java.util.ArrayList;
import java.util.List;

public class Foo {
    private List<String> data;

    public static void main(String[] args) {
        Foo f = new Foo();
        for (int i = 0; i < 1000; i++) {
            int n = i;
            new Thread(() -> {
                while (true) {
                    f.update();
                    System.gc();
                    System.out.println(n + " updated");
                }
            }).start();
        }
    }

    public synchronized void update() {
        data = new ArrayList<>(1024 * 1024);
        for (int i = 0; i < 1024 * 1024; i++) {
            data.add(new String("hello"));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

fgh*_*ghj 5

所以问题出在大量的 glibc 的 malloc arena 上,每个 arena 都有预分配内存的缓存。检查它的简单方法是使用 运行二进制文件MALLOC_ARENA_MAX=2,但最终解决方案取决于使用模式,有很多变量可以调整 glibc 的分配器:http ://man7.org/linux/man-pages/man3/mallopt.3.html ://man7.org/linux/man-pages/man3/mallopt.3.html 。

Java虚拟机实际上也受到malloc分配器的影响。根据我一段时间的经验,配置 arenas 的数量以防止 docker 内的 jvm 占用大量内存是合适的。