Как решить ошибку pthread_create (11) ресурс временно недоступен? - PullRequest
0 голосов
/ 16 июня 2020

Я создаю проект на языке c (используя openwrt в качестве ОС) для загрузки файлов на FTP-сервер. и я использую MQTT для входящих данных. Итак, для каждой topi c, на которую я подписан, я сохраняю эти данные, а затем загружаю их на FTP-сервер и, чтобы все шло гладко, каждый раз, когда мне нужно загрузить файл, я просто использую поток для выполнения работы. и чтобы убедиться, что программа не запускает слишком много потоков, каждому topi c разрешено создать один поток. и я использую переменную (например, мьютекс, но это не pthread_mutex_t, потому что мне не нужно блокировать поток, я хочу пропустить этот шаг и загрузить следующий файл). Я, хотя с этой техникой я в безопасности, но после запуска программы в течение 15 минут я получаю эту ошибку 11, в которой говорится, что ресурс временно недоступен, когда программа пытается создать поток (pthread_create). одна из моих попыток выяснить, в чем может быть проблема.

  • Я использовал функцию pthread_join (), которая не является вариантом в моем состоянии, но просто чтобы убедиться, что каждый поток завершен, а не обкатка на постоянном л oop. программа работала более часа, и ошибка больше не появлялась. и, конечно же, каждый поток был завершен, как и предполагалось.
  • и я на 90% уверен, что каждый topi c создается только в потоке, а следующий будет создан только в случае завершения предыдущего. (я отслеживал состояние переменной до и после создания потока).
  • Я установил максимальный поток отсюда "/ proc / sys / kernel / threads-max" равным 2000 (2000 более чем достаточно поскольку у меня не слишком много тем)

функция загрузки (это создаст цепочку):

void uploadFile(<args...>, bool* locker_p){
    *locker_p = true;
    args->uploadLocker_p = uploadLocker_p;

    <do something here>

    pthread_t tid;
    int error = pthread_create(&tid, NULL, uploadFileThread, (void*)args);
        if(0 != error){
            printf("Couldn't run thread,(%d) => %s\n", error, strerror(error));
    }
        else{
            printf("Thread %d\n", tid);
    }
}

ветка загрузки:

void *uploadFileThread(void *arg){ 
    typeArgs* args = (typeArgs*)arg;

   <do something like upload the file>

    *(args->uploadLocker_p) = false;
    free(args);

    return NULL;
    //pthread_exit(0);
}

1 Ответ

1 голос
/ 16 июня 2020

Размер стека по умолчанию для созданных потоков потребляет слишком много виртуальной памяти.

По сути, ядро ​​сообщает вашему процессу, что у него уже используется столько виртуальной памяти, что оно не осмеливается отдавать ее больше, потому что не хватает ОЗУ и подкачки для ее резервного копирования, если процесс должен был внезапно использовать все это.

Чтобы исправить это, создайте атрибут, который ограничивает стек для каждого потока чем-то разумным. Если ваши потоки не используют массивы в качестве локальных переменных или не выполняют глубокую рекурсию, тогда 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);.

Когда появится новое соединение, и оно будет определено как то, которое должно быть делегировано рабочим потокам, вы можете , но не обязательно, проверять количество незанятых потоков, создавать новые рабочие потоки, отклонять загрузку или просто добавлять работу в очередь, чтобы она «в конечном итоге» была обработана.

...