5

我正在运行以下程序。它只是创建立即死亡的线程。

我发现,在 93 到 98 次(略有不同)成功调用之后,每次对 pthread_create() 的下一次调用都会失败,并出现错误 11:资源暂时不可用。我想我正确地关闭了线程,所以它应该放弃它拥有的任何资源,但一些资源变得不可用。

程序的第一个参数允许我设置调用 pthread_create() 之间的间隔,但使用不同的值进行测试,我了解到间隔无关紧要(好吧,我会更早得到错误):成功的次数调用将是相同的。

该程序的第二个参数允许我在呼叫失败后设置睡眠间隔,但间隔的长度似乎没有任何区别。

我在这里撞到哪个天花板?

编辑:在 doSomething(): change lock to unlock 中发现错误,程序运行正常。问题仍然存在:由于错误未纠正而耗尽了哪些资源?

#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, %m\n", threadsCreated, errno);
            milliSleep(atoi(argv[2]));
        }
        else threadsCreated++;
        milliSleep(atoi(argv[1]));
    }
}
4

2 回答 2

5

如果您使用的是 32 位发行版,您可能会遇到地址空间限制。我最后一次检查,glibc 将在创建的每个线程中分配大约 13MB 的堆栈空间(这只是映射的大小,而不是分配的内存)。使用 98 个线程,您将超过 3G 可用地址空间的千兆字节。

sleep(1000000)您可以通过在错误(例如或其他)之后冻结您的进程并使用pmap.

如果这是问题所在,请尝试使用传递给pthread_attr_setstack()的设置较小的堆栈大小。显然,您必须判断您的堆栈要求,但通常即使是复杂的代码也只能在几千字节的堆栈中成功运行。pthread_attr_tpthread_create

于 2012-10-11T16:57:01.400 回答
2

您的程序不会“创建简单消失的线程”。它不会做你认为它会做的事情。

首先,pthread_mutex_unlock()仅解锁pthread_mutex_t已被同一线程锁定的 a 。这就是互斥锁的工作方式:它们只能由锁定它们的同一线程解锁。如果您想要信号量的行为使用信号量

您的示例代码创建了一个递归互斥锁,该doSomething()函数尝试锁定该互斥锁。因为它由原始线程持有,所以它阻塞(等待互斥体在pthread_mutex_lock()调用中变为空闲)。doSomethingLock因为原始线程从不释放锁,所以您只需在互斥体上堆积新线程。

互斥锁的递归性只是意味着一个线程可以多次锁定它;它必须解锁它相同的次数才能实际释放互斥锁。

如果您将pthread_mutex_lock()in更改doSomething()pthread_mutex_unlock(),那么您正在尝试解锁该线程未持有的互斥锁。调用失败,然后线程立即死亡。


假设您修复了您的程序,接下来您会发现您不能创建超过一百个左右的线程(取决于您的系统和可用 RAM)。

Andy Ross 很好地解释了原因:固定大小的堆栈(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,但我建议使用较大的 2 次方。(在任何二进制架构上,页面大小始终是 2 的幂。)

它只是最小值,pthread 库可以自由使用它认为合适的任何更大的值,但实际上堆栈大小似乎是您设置的值,加上一个取决于体系结构、内核和 pthread 的固定值库版本。使用编译时常量几乎适用于所有情况,但如果您的应用程序足够复杂以至于有一个配置文件,最好让用户在配置中覆盖编译时常量(如果他们愿意)文件。

于 2012-10-11T22:30:58.417 回答