Na prática, é difícil (e às vezes impossível) aumentar a pilha. Para entender o porquê, é necessário entender a memória virtual.
Nos Ye Olde Days de aplicativos de thread único e memória contígua, três eram três componentes de um espaço de endereço do processo: o código, o heap e a pilha. Como os três foram organizados depende do sistema operacional, mas geralmente o código veio primeiro, começando na parte inferior da memória, o heap veio a seguir e cresceu, e a pilha começou no topo da memória e cresceu para baixo. Havia também alguma memória reservada para o sistema operacional, mas podemos ignorá-lo. Os programas naqueles dias tinham estouros de pilha um pouco mais dramáticos: a pilha colidia com a pilha e, dependendo de qual fosse a atualização primeiro, você trabalharia com dados incorretos ou retornaria de uma sub-rotina para uma parte arbitrária da memória.
O gerenciamento de memória mudou um pouco esse modelo: da perspectiva do programa, você ainda tinha os três componentes de um mapa de memória de processo, e eles geralmente eram organizados da mesma maneira, mas agora cada um dos componentes era gerenciado como um segmento independente e a MMU sinalizaria o SO se o programa tentou acessar a memória fora de um segmento. Depois de ter memória virtual, não havia necessidade ou desejo de conceder a um programa acesso a todo o espaço de endereço. Portanto, os segmentos receberam limites fixos.
Então, por que não é desejável conceder a um programa acesso ao seu espaço de endereço completo? Porque essa memória constitui uma "carga de confirmação" contra a troca; a qualquer momento, qualquer ou toda a memória de um programa pode ter que ser gravada para ser trocada para liberar espaço para a memória de outro programa. Se todo programa pudesse consumir 2 GB de swap, seria necessário fornecer swap suficiente para todos os seus programas ou aproveitar a chance de dois programas precisarem de mais do que poderiam obter.
Nesse ponto, assumindo espaço de endereço virtual suficiente, você pode estender esses segmentos, se necessário, e o segmento de dados (heap) de fato cresce com o tempo: você começa com um pequeno segmento de dados e quando o alocador de memória solicita mais espaço quando é necessário. Nesse ponto, com uma única pilha, seria fisicamente possível estender o segmento da pilha: o sistema operacional poderia impedir a tentativa de empurrar algo para fora do segmento e adicionar mais memória. Mas isso também não é particularmente desejável.
Digite multiencadeamento. Nesse caso, cada encadeamento possui um segmento de pilha independente, novamente com tamanho fixo. Mas agora os segmentos são dispostos um após o outro no espaço de endereço virtual; portanto, não há como expandir um segmento sem mover outro - o que você não pode fazer porque o programa potencialmente terá ponteiros para a memória que está na pilha. Como alternativa, você pode deixar algum espaço entre os segmentos, mas esse espaço seria desperdiçado em quase todos os casos. Uma abordagem melhor era sobrecarregar o desenvolvedor de aplicativos: se você realmente precisasse de pilhas profundas, poderia especificar isso ao criar o encadeamento.
Hoje, com um espaço de endereço virtual de 64 bits, poderíamos criar pilhas efetivamente infinitas para números efetivamente infinitos de threads. Mas, novamente, isso não é particularmente desejável: em quase todos os casos, uma sobrecarga de pilha indica um erro no seu código. O fornecimento de uma pilha de 1 GB simplesmente adia a descoberta desse bug.