3 ms·
> The first issue you run into is the thread stack size - Linux has a hard default of 8MB; at 100,000 you start to hit OOM issues first. Go (and BEAM) has a to
by YorickPeterse 6y ago
> The first issue you run into is the thread stack size - Linux has a hard default of 8MB; at 100,000 you start to hit OOM issues first. Go (and BEAM) has a ton of work to make this use case possible within the runtime.
This is not how thread stack sizes work. Thread stack sizes allocate a certain
amount of _virtual_ memory. This means that with a stack size of 8MB you don't
actually immediately use 8MB of physical memory. Only when you start filling up
that memory do you start using physical memory. We can easily demonstrate this
using a simple Rust program:
use std::thread;
use std::time::Duration;
fn main() {
let mut handles = Vec::with_capacity(10_000);
for _ in 0..handles.capacity() {
let handle = thread::spawn(|| thread::sleep(Duration::from_secs(1)));
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
We when compile this in release mode, and run it as follows:
/usr/bin/time -f '%M KB RSS' program-name
On my Linux desktop this prints out:
88492 KB RSS
This is roughly 86 MB or RSS memory being used. If the thread stacks would use
physical memory, this would translate to just under 80 GB; and the program would
have been OOM killed because I only have 16 GB of RAM available.
The virtual memory limits in turn are not much of an issue. I think on most 64
bits systems the virtual memory limit is 256 TB. A 256 TB limit would allow up
to 33 554 432 OS threads with a stack size of 8MB. And since you can configure
the limit when spawning threads, you can increase this number by spawning
threads with a smaller limit (e.g. 4MB).