Почему несколько потоков используют слишком много памяти при удерживании Mutex - PullRequest
4 голосов
/ 26 сентября 2019

Ниже код использует ~ 150 МБ в одном потоке, но использует несколько ГБ в 100 потоках:

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],
        }
    }
}

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

Может кто-нибудь объяснить, почему этот код использует столько памяти?

Подобный кодв Java сохраняет память ~ 200 МБ даже при 1000 потоках.

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"));
        }
    }
}

1 Ответ

2 голосов
/ 26 сентября 2019

Таким образом, проблема заключалась в большом количестве malloc-арен glibc, каждая арена имеет кеш предварительно выделенной памяти.Простой способ проверить это - запустить двоичный файл с MALLOC_ARENA_MAX=2, но окончательное решение зависит от модели использования, есть много переменных для настройки распределителя glibc: http://man7.org/linux/man-pages/man3/mallopt.3.html.

Виртуальная машина Java такжефактически зависит от распределителя malloc.По моему опыту, когда-нибудь целесообразно настроить количество арен, чтобы предотвратить огромное использование памяти jvm внутри докера.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...