Размер стека по умолчанию для созданных потоков потребляет слишком много виртуальной памяти.
По сути, ядро сообщает вашему процессу, что у него уже используется столько виртуальной памяти, что оно не осмеливается отдавать ее больше, потому что не хватает ОЗУ и подкачки для ее резервного копирования, если процесс должен был внезапно использовать все это.
Чтобы исправить это, создайте атрибут, который ограничивает стек для каждого потока чем-то разумным. Если ваши потоки не используют массивы в качестве локальных переменных или не выполняют глубокую рекурсию, тогда 2*PTHREAD_STACK_MIN
(от <limits.h>
) будет хорошим размером. Атрибут не используется вызовом pthread_create()
, это просто блок конфигурации, и вы можете использовать тот же самый для любого количества создаваемых вами потоков или создать новый для каждого потока.
Пример :
pthread_attr_t attrs;
pthread_t tid;
int err;
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 2 * PTHREAD_STACK_MIN);
err = pthread_create(&tid, &attrs, uploadFileThread, (void *)args);
pthread_attr_destroy(&attrs);
if (err) {
/* Failed, errno in err; use strerror(err) */
} else {
/* Succeeded */
}
Также помните, что если ваш uploadFileThread()
выделяет память, она не будет автоматически освобождаться при выходе из потока. Похоже, OP уже знает об этом (поскольку у них есть функция, освобождающая структуру аргументов, когда она готова к выходу), но я подумал, что стоит указать на это.
Лично мне нравится вместо этого используйте пул потоков. Идея в том, что загрузочные воркеры создаются заранее, и они будут ждать нового задания. Вот пример:
pthread_mutex_t workers_lock;
pthread_mutex_t workers_wait;
volatile struct work *workers_work;
volatile int workers_idle;
volatile sig_atomic_t workers_exit = 0;
где struct work
- односвязный список, защищенный workers_lock
, workers_idle
инициализируется нулем и увеличивается при ожидании новой работы, workers_wait
- это переменная состояния, о которой сообщается, когда новая работа прибывает ниже workers_lock
, а workers_exit
- это счетчик, который, когда не равен нулю, сообщает, что многие рабочие должны выйти.
Рабочий будет в основном чем-то вроде
void worker_do(struct work *job)
{
/* Whatever handling a struct job needs ... */
}
void *worker_function(void *payload __attribute__((unused)))
{
/* Grab the lock. */
pthread_mutex_lock(&workers_lock);
/* Job loop. */
while (!workers_exit) {
if (workers_work) {
/* Detach first work in chain. */
struct work *job = workers_work;
workers_work = job->next;
job->next = NULL;
/* Work is done without holding the mutex. */
pthread_mutex_unlock(&workers_lock);
worker_do(job);
pthread_mutex_lock(&workers_lock);
continue;
}
/* We're idle, holding the lock. Wait for new work. */
++workers_idle;
pthread_cond_wait(&workers_wait, &workers_lock);
--workers_idle;
}
/* This worker exits. */
--workers_exit;
pthread_mutex_unlock(&workers_lock);
return NULL;
}
Процесс обработки соединения может использовать idle_workers()
для проверки количества простаивающих рабочих и либо увеличить пул рабочих потоков, либо отклонить соединение как слишком загруженное. idle_workers()
- это что-то вроде
static inline int idle_workers(void)
{
int result;
pthread_mutex_lock(&workers_lock);
result = workers_idle;
pthread_mutex_unlock(&workers_lock);
return result;
}
Обратите внимание, что каждый воркер удерживает блокировку только на очень короткое время, поэтому вызов idle_workers()
не будет блокироваться надолго. (pthread_cond_wait()
атомарно снимает блокировку, когда начинает ждать сигнала, и возвращается только после повторного получения блокировки.)
При ожидании нового соединения в accept()
установите сокет неблокирующим и используйте poll()
для ожидания новых подключений. Если время ожидания истекло, проверьте количество рабочих процессов и при необходимости уменьшите их, позвонив reduce_workers(1)
или аналогичным образом:
void reduce_workers(int number)
{
pthread_mutex_lock(&workers_lock);
if (workers_exit < number) {
workers_exit = number;
pthread_cond_broadcast(&workers_wait);
}
pthread_mutex_unlock(&workers_lock);
}
Чтобы избежать вызова pthread_join()
для каждого потока - а мы действительно этого не делаем. я даже не знаю, какие потоки здесь вышли! - чтобы получить / освободить ядро и C метаданные библиотеки, относящиеся к потоку, рабочие потоки должны быть отсоединены . После успешного создания рабочего потока tid
просто вызовите pthread_detach(tid);
.
Когда появится новое соединение, и оно будет определено как то, которое должно быть делегировано рабочим потокам, вы можете , но не обязательно, проверять количество незанятых потоков, создавать новые рабочие потоки, отклонять загрузку или просто добавлять работу в очередь, чтобы она «в конечном итоге» была обработана.