98-й вызов функции pthread create() завершается ошибкой
Я запускаю следующую программу. Он просто создает нити, которые сразу же умирают.
Я обнаружил, что после 93-98 (это немного отличается) успешных вызовов, каждый следующий вызов pthread_create () завершается ошибкой 11: ресурс временно недоступен. Я думаю, что правильно закрываю поток, поэтому он должен отказаться от любых ресурсов, которые у него есть, но некоторые ресурсы становятся недоступными.
Первый параметр программы позволяет мне установить интервал между вызовами в pthread_create() но тестируя с разными значениями, я узнал, что интервал не имеет значения (ну, я получу ошибку раньше): количество успешных вызовов будет одинаковым.
Второй параметр программы позволяет мне установить интервал сна после неудачного вызова, но длина интервала, по-видимому, не имеет никакого значения.
В какой потолок я здесь ударяюсь?
EDIT: обнаружена ошибка в doSomething (): измените блокировку на разблокировку, и программа работает нормально. Остается вопрос: какой ресурс исчерпан при неисправленной ошибке?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <pthread.h>
#include <errno.h>
pthread_mutex_t doSomethingLock;
void milliSleep(unsigned int milliSeconds)
{
struct timespec ts;
ts.tv_sec = floorf(((float)milliSeconds / 1000));
ts.tv_nsec = ((((float)milliSeconds / 1000) - ts.tv_sec)) * 1000000000;
nanosleep(&ts, NULL);
}
void *doSomething(void *args)
{
pthread_detach(pthread_self());
pthread_mutex_lock(&doSomethingLock);
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_t doSomethingThread;
pthread_mutexattr_t attr;
int threadsCreated = 0;
if (argc != 3)
{
fprintf(stderr, "usage: demo <interval between pthread_create() in ms> <time to wait after fail in ms>n");
exit(1);
}
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&doSomethingLock, &attr);
while (1)
{
pthread_mutex_lock(&doSomethingLock);
if (pthread_create(&doSomethingThread, NULL, doSomething, NULL) != 0)
{
fprintf(stderr, "%d pthread_create(): error %d, %mn", threadsCreated, errno);
milliSleep(atoi(argv[2]));
}
else threadsCreated++;
milliSleep(atoi(argv[1]));
}
}
2 ответов:
Если вы находитесь на 32-битном дистрибутиве, вы, вероятно, достигаете пределов адресного пространства. Последний раз я проверял, что glibc выделит около 13 Мб для стекового пространства в каждом созданном потоке (это просто размер отображения, а не выделенная память). С 98 потоками, вы будете проталкивать мимо гигабайта адресного пространства доступного 3G.
Вы можете проверить это, заморозив процесс после ошибки (например,
sleep(1000000)или что-то еще) и глядя на его адресное пространство с помощьюpmap.Если это затем попробуйте установить меньший размер стека с помощью
pthread_attr_setstack()наpthread_attr_t, который вы передаете вpthread_create. Очевидно, вам придется судить о ваших требованиях к стеку, но часто даже сложный код может успешно работать только в нескольких килобайтах стека.
Ваша программа не " создает потоки, которые просто замирают". Он не делает того, что вы думаете.
Во-первых,
pthread_mutex_unlock()разблокирует толькоpthread_mutex_t, который был заблокировантем же потоком . Вот как работают мьютексы: они могут быть разблокированы только тем же потоком, который их заблокировал. Если вы хотите поведение семафора семафор, используйте семафор .Ваш пример кода создает рекурсивный мьютекс, который функция
Рекурсивность по отношению к мьютексам просто означает, что поток может заблокировать его несколько раз; он должен разблокировать его столько же раз, чтобы мьютекс был фактически освобожден.doSomething()пытается замок. Поскольку он удерживается исходным потоком, он блокируется (ждет, пока мьютекс освободится в вызовеpthread_mutex_lock()). Поскольку исходный поток никогда не освобождает блокировку, вы просто накапливаете новые потоки поверхdoSomethingLockмьютекса.Если вы измените
pthread_mutex_lock()вdoSomething()наpthread_mutex_unlock(), то вы пытаетесь разблокировать мьютекс не держится за эту нить. Вызов завершается неудачей, изатем потоки умирают немедленно.
Предположив, что вы исправите свою программу, вы обнаружите, что вы не можете создать более сотни потоков (в зависимости от вашей системы и доступной оперативной памяти).
Причина хорошо объяснена Энди Россом: стеки фиксированного размера (getrlimit(RLIMIT_STACK, (struct rlimit *)&info)говорит вам, сколько, если вы не установите его с помощью атрибутов потока) съедают ваше доступное адресное пространство.Исходный стек, заданный процесс изменяется автоматически, но для всех остальных потоков размер стека фиксирован. По умолчанию он очень большой; в моей системе 8388608 байт (8 мегабайт).
Я лично создаю потоки с очень маленькими стеками, обычно 65536 байт, что более чем достаточно, если ваши функции не используют локальные массивы или большие структуры, или не делают безумно глубокую рекурсию:
#ifndef THREAD_STACK_SIZE #define THREAD_STACK_SIZE 65536 #endif pthread_attr_t attrs; pthread_t thread[N]; int i, result; /* Create a thread attribute for the desired stack size. */ pthread_attr_init(&attrs); pthread_attr_setstacksize(&attrs, THREAD_STACK_SIZE); /* Create any number of threads. * The attributes are only a guide to pthread_create(), * they are not "consumed" by the call. */ for (i = 0; i < N; i++) { result = pthread_create(&thread[i], &attrs, some_func, (void *)i); if (result) { /* strerror(result) describes the error */ break; } } /* You should destroy the attributes when you know * you won't be creating any further threads anymore. */ pthread_attr_destroy(&attrs);Минимальный размер стека должен быть доступен как
PTHREAD_STACK_MIN, и должен быть кратенsysconf(_SC_PAGESIZE). В настоящее времяPTHREAD_STACK_MIN == 16384, но я рекомендуется использовать большую мощность из двух. (Размеры страниц всегда степени двойки на любой двоичной архитектуре.)Это только минимум, и библиотека pthread может использовать любое большее значение, которое она считает нужным, но на практике размер стека кажется таким, как вы его задаете, плюс фиксированное значение в зависимости от архитектуры, ядра и версии библиотеки pthread. Использование константы времени компиляции хорошо работает почти во всех случаях, но если ваше приложение достаточно сложное, чтобы иметь конфигурационный файл, возможно, было бы хорошей идеей позволить пользователю переопределить константу времени компиляции, если они хотят, в конфигурационном файле.
Comments