LINUX.ORG.RU

Завершение потока при блокировке


0

0

Каким образом корректно завершить поток, который висит на блокирующей функции? например на read, ожидания семафора или аналогичных.. Т.е. например есть процесс в котором несколько потоков, висящих на таких блокирующих функциях? Потоки создаются с такими параметрами PTHREAD_CREATE_DETACHED, PTHREAD_EXPLICIT_SCHED, SCHED_OTHER. Вызов phread_kill(..,SIGINT) почему-то завершает и основной процесс, а на вызов pthread_exit ругается система


Ответ на: комментарий от tailgunner

pthread_cancel работает только в случае когда функция потока вызывает pthread_testcancel. а вопрос когда функция потока висит на блокировке

например

while(1){ int rd = read(....); // поток на блокирующей функции pthread_testcancel(); // а сюда дойдем только когда что-то прочитаем . .. }

ну и с аналогичными функциями так же (семафоры, каналы и т.д.)

вопрос остается открытым

masta
() автор топика
Ответ на: комментарий от masta

Не стану врать, что часто пользуюсь pthread_cancel, но... ты сам-то попробовал? :) по стандарту, pthread_cancel завершает нить, находящуюся в cancellation point - так вот, read является cancellation point (ими являются практически все функции, включающие в себя ожидание).

> pthread_cancel работает только в случае когда функция потока вызывает pthread_testcancel

pthread_testcancel предназначена для проверки и исполнения cancellation request в коде, где долго не встречаются cancellation point (например, в вычислениях).

info libc "POSIX Threads"

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

спасибо tailgunner. все верно, проверено. итак резюме:

while(1){ ..1.. block_function(); // read, sem_wait(), pthread_mutex_lock etc ..2.. pthread_testcancel(); ..3.. } важно другое - когда вы находитесь в блоке ..2.. вы должны проверять корректно ли вы вышли из блокирующей функции - иначе не избежать логической ошибки. тема закрыта

masta
() автор топика
Ответ на: комментарий от masta

> while(1){ ..1.. block_function(); // read, sem_wait(), pthread_mutex_lock etc ..2.. pthread_testcancel(); ..3.. } важно другое - когда вы находитесь в блоке ..2.. вы должны проверять корректно ли вы вышли из блокирующей функции - иначе не избежать логической ошибки. тема закрыта

hm... ну вообще-то это как-бы подразумевалось и о прерванном read(2) вы узнаете лишь по ошибке и значению EINTR в errno... ;)

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от masta

> вы должны проверять корректно ли вы вышли из блокирующей функции

если нить cancelled, то ты можешь сделать эту проверку только в cancellation cleanup handler: pthread_cleanup_{push,pop}.

tailgunner ★★★★★
()

а вообще-то асинхронное завершение нитей - зло.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> если нить cancelled, то ты можешь сделать эту проверку только в cancellation cleanup handler: pthread_cleanup_{push,pop}.

ну если вам таки угораздило по каким-то причинам связаться с cancellation points то да, тут можно неплохо огрести при малейшей неосторожности. хотя разумнее IMHO подружиться с pthread_kill() а в подобытном потоке аккуратно обрабатывать коды ошибок из тех-же системных вызовов и делать соотв. cleanup.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

> ну если вам таки угораздило по каким-то причинам связаться с cancellation points то да, тут можно неплохо огрести при малейшей неосторожности. хотя разумнее IMHO подружиться с pthread_kill()

вроде бы штатный способ насильственного заврешения нити - это pthread_cancel? нити и сигналы - это тоже способ огрести :) Вообще я плохо представляю, зачем нужно завершать нити насильственно.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> вроде бы штатный способ насильственного заврешения нити - это pthread_cancel? нити и сигналы - это тоже способ огрести :) Вообще я плохо представляю, зачем нужно завершать нити насильственно.

дык в том то и суть, что её не нужно завершать столь брутально как это может делать TerminateThread(2) в Win32 или же прибивающий сигнал в *NIX. кооперативно нужно завершаться и по-возможности подчищать за собой всякие нехорошие вещи.

другое дело, что если поток висит в блокирующем read() и будет висеть в нем непонятно сколько а нам уже пора выходить, read - а не поток! - нужно как-то прервать чтобы дать потоку возможность выйти. и тут посылка сигнала конкретному потоку работает вполне даже на ура. впрочем, наверное и с точками отмены то-же можно навернуть нечто аналогичное.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

> если поток висит в блокирующем read() и будет висеть в нем непонятно сколько а нам уже пора выходить, read - а не поток! - нужно как-то прервать чтобы дать потоку возможность выйти. и тут посылка сигнала конкретному потоку работает вполне даже на ура

а я бы просто закрыл дескриптор "под ногами" у нити :)

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> а я бы просто закрыл дескриптор "под ногами" у нити :)

a) что-то меня настораживает этот метод. хотя не исключего, что как-то он и прокатит. ладно, если это сокет то можни и shutdown() сказать, а вот на счет файлового дескриптора есть определенные сомнения.
b) anyway, родительский поток P может и не знать о всех ресурсках, которые наплодили его дочерние потоки C. в принципе запросто. что тогда будет закрывать?

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от tailgunner

> а я бы просто закрыл дескриптор "под ногами" у нити :)

ну и опять же, если она висит на select(2)? кого будем убивать? всех? первого попавшегося? а если он висит на sigwait(2)?

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

> ну и опять же, если она висит на select(2)? кого будем убивать? всех? первого попавшегося? а если он висит на sigwait(2)?

ну, там-то они сами отвалятся по тайм-аутам, посмотрят на глобальную переменную pleasequit, и примут правильное решение :)

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> ну, там-то они сами отвалятся по тайм-аутам, посмотрят на глобальную переменную pleasequit, и примут правильное решение :)

по какому-такому таймауту? а нету у них таймаута в select-е дабы не плодить лишний оверхед на поллинг... или же он есть, но его значение слишком большое и не удовлетворяет требования родителя по временным рамкам завершения дочерних процессов.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

> по какому-такому таймауту? а нету у них таймаута в select-е дабы не плодить лишний оверхед на поллинг...

поллинг? o_O оверхэд? имеется в виду, что иногда (один-два раза в секунду, скажем) будет возврат по тайм-ауту?

> не удовлетворяет требования родителя по временным рамкам завершения дочерних процессов.

если есть требование завершаться мгновенно, тогда ой. просто я сам никогда такого не встречал.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> поллинг? o_O оверхэд? имеется в виду, что иногда (один-два раза в секунду, скажем) будет возврат по тайм-ауту?

...а потоков у нас, допустим, тысячи и каждый из них раз в секунду прерывается по таймауту в select-е просто проверить, а не позвали ли его выйти с вероятностью 1/10^5...?

> если есть требование завершаться мгновенно, тогда ой. просто я сам никогда такого не встречал.

ну мгновенно не мгновенно но хотя бы с той реакцией, которую при данной загрузке в принципе может обеспечить заданная система. в случае же с периодическим опросом некого флага "а не пора ли выйти?" система уже ни чем не сможет помочь бо ограничения по реакции зашиты в само приложение.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

>> поллинг? o_O оверхэд? имеется в виду, что иногда (один-два раза в секунду, скажем) будет возврат по тайм-ауту?

>...а потоков у нас, допустим, тысячи и каждый из них раз в секунду прерывается по таймауту в select-е

если уж приложению и в самом деле нужны тысячи нитей, и они постоянно заняты делом, подозреваю, что оверхэд на обработки тайм-аутов будет доли процента :) Кроме того, для экстремальных случаев можно сделать и по-другому - выделить 1 внутренний пайп для сигнала выхода, и писать в него столько байтов, сколько в приложении нитей.

>> если есть требование завершаться мгновенно, тогда ой. просто я сам никогда такого не встречал.

>ну мгновенно не мгновенно но хотя бы с той реакцией, которую при данной загрузке в принципе может обеспечить заданная система

А не будет нескромным спросить, в каких случаях необходима такая резвость завершения работы приложения?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> если уж приложению и в самом деле нужны тысячи нитей, и они постоянно заняты делом, подозреваю, что оверхэд на обработки тайм-аутов будет доли процента :)

ну вообще-то я не утверждал, что он все постоянно заняты каким-то делом. есть сервер с массой сетевых соединений, каждое соединение обслуживается своим потоком. активность единичного соединения, допустим, задаётся удалённым клиентом и в среднем невелика.

ну допустим, что это FTP сервер и основную массу времени пользователь проводит в блужданиях по директориям. или это telnet/ssh сервер, где [в идеале] юзер в основном обдумывает результат вывода последней команды. в любом случае, одновременно подключённых пользователей может быть очень много но средний трафик небольшим -> единичная нагрузка на сервер невелика.

даже при весьма значительном количестве пользователей, хост может в принципе находиться почти в idle бо сами по себе без инициации от пользователей потоки не кушают процессорного времени. но вот если каждый из них вдруг начнёт какую-то самодеятельность aka просыпаться по фиксированному таймауту и что-то делать, холостая нагрузка заметно возрастёт. и я не думаю, что это будут такие уж доли процента. то надо?

> Кроме того, для экстремальных случаев можно сделать и по-другому - выделить 1 внутренний пайп для сигнала выхода, и писать в него столько байтов, сколько в приложении нитей.

да уж, это явно чуток экстремально ;)

> А не будет нескромным спросить, в каких случаях необходима такая резвость завершения работы приложения?

собственно а почему должно быть верно обратно? я послал SIGTERM на food и ожидаю, что он как можно быстрее завершит свою работу. если он это может сделать в течении, допустим, половины секунды, почему я должен ждать больше?

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от tailgunner

По-моему удобное решение

С помощью нескольких вызовов pthread_cleanup_push() сохраняем записи о каждом ресурсе, которым владеет поток. Когда ресурсы не нужны - освобождаем в обратном порядке вызовами pthread_cleanup_pop(1).

Похоже на try {} __finally {} в C++. Или как делают тоже самое кернел-хакеры с помощью goto.

Dr_ZLO
()
Ответ на: комментарий от klalafuda

>> Кроме того, для экстремальных случаев можно сделать и по-другому - выделить 1 внутренний пайп для сигнала выхода, и писать в него столько байтов, сколько в приложении нитей.

>да уж, это явно чуток экстремально ;)

Экстремальным задачам - экстремальные решения! :)

>> А не будет нескромным спросить, в каких случаях необходима такая резвость завершения работы приложения?

>собственно а почему должно быть верно обратно?

Оно не то чтобы должно... просто who cares? остановка сервиса - событие редкое. В жизни я встречал в ТЗ требования, чтобы приложение стартовало за некоторое небольшое время, но насчет остановки никто особо не волновался - думаю, несколько секунд были бы вполне приемлимы.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> если уж приложению и в самом деле нужны тысячи нитей, и они постоянно заняты делом, подозреваю, что оверхэд на обработки тайм-аутов будет доли процента :) Кроме того, для экстремальных случаев можно сделать и по-другому - выделить 1 внутренний пайп для сигнала выхода, и писать в него столько байтов, сколько в приложении нитей.

впрочем, по крайней мере в первом приближении предположение о том, что периодический опрос чего-то по таймауту в select-е в контексте каждого из потоков не займёт сильно много процессорного времени вполне корректно. по крайней мере по тесту, который приведён ниже, на примерно 305ти потоках даже при таймауте ~10ms система не грузится больше чем на 1% если судить по top.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

--- main.c ---
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>

static void *thread_func(void *arg);
static void signal_handler(int signo);

struct thread_cont {
    unsigned int timeout, id;
    int fd, is_joinable;
    pthread_t pth;
};

int
main(int argc, char *argv[])
{
    unsigned int req_threads = 10, timeout = 0, num_threads, left;
    struct thread_cont *threads, *tc;
    int signo, opt, error, fds[2];
    struct sigaction sa;
    sigset_t ss;
    size_t size;

    while ((opt = getopt(argc, argv, "n:t:")) != -1) {
        switch (opt) {
        case 'n' :
            if (sscanf(optarg, "%u", &req_threads) != 1 || !req_threads) {
                printf("Error: Invalid number of threads %s\n", optarg);
                return EXIT_FAILURE;
            }
            break;
        case 't' :
            if (sscanf(optarg, "%u", &timeout) != 1) {
                printf("Error: Invalid timeout %s\n", optarg);
                return EXIT_FAILURE;
            }
            break;
        default :
            printf("Error: Unknown command line argument\n");
            return EXIT_FAILURE;
        }
    }

    printf("Options: num threads %u timeout %ums\n",
        req_threads, timeout);

    if (pipe(fds) == -1) {
        printf("Error: Failed to create pipe (%s)\n",
            strerror(errno));
        return EXIT_FAILURE;
    }


    size = req_threads * sizeof(*threads);
    threads = malloc(size);
    if (!threads) {
        printf("Error: Failed to alloc threads context (%s)\n",
            strerror(errno));
        return EXIT_FAILURE;
    }
    memset(threads, 0, size);

    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigaction(SIGINT, &sa, 0);
    sigaction(SIGTERM, &sa, 0);
    sigaction(SIGUSR1, &sa, 0);

    sigemptyset(&ss);
    sigaddset(&ss, SIGINT);
    sigaddset(&ss, SIGTERM);
    sigaddset(&ss, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &ss, 0);

    num_threads = 0;
    left = req_threads;
    tc = threads;
    while (left) {
        tc->id = req_threads - left;
        tc->timeout = timeout;
        tc->fd = fds[0];
        error = pthread_create(&tc->pth, 0, thread_func, tc);
        if (!error) {
            tc->is_joinable = 1;
            num_threads++;
        } else {
            printf("Error: Failed to create thread %u (%s)\n",
                tc->id, strerror(error));
        }
        left--, tc++;
    }

    printf("Start %u child threads\n", num_threads);
    printf("Suspend main thread waiting for signal\n");
    sigemptyset(&ss);
    sigaddset(&ss, SIGINT);
    sigaddset(&ss, SIGTERM);
    sigwait(&ss, &signo);
    printf("Main thread aborted by signal %d\n", signo);

    num_threads = 0;
    left = req_threads;
    tc = threads;
    while (left) {
        if (tc->is_joinable) {
            pthread_kill(tc->pth, SIGUSR1);
            error = pthread_join(tc->pth, 0);
            if (!error) {
                num_threads++;
            } else {
                printf("Error: Failed to join thread %u (%s)\n",
                    tc->id, strerror(error));
            }
        }
        left--, tc++;
    }
    printf("Join %u child threads\n", num_threads);

    printf("Terminate main thread\n");

    return EXIT_SUCCESS;
}

static void *
thread_func(void *arg)
{
    struct thread_cont *tc = arg;
    struct timeval tv;
    int dummy_counter = 0;
    sigset_t ss;
    fd_set rfds;
    char buf;
    int rc;

    sigemptyset(&ss);
    sigaddset(&ss, SIGUSR1);
    pthread_sigmask(SIG_UNBLOCK, &ss, 0);

    while (1) {
        FD_ZERO(&rfds);
        FD_SET(tc->fd, &rfds);
        if (!tc->timeout) {
            rc = select(tc->fd + 1, &rfds, 0, 0, 0);
        } else {
            tv.tv_sec = tc->timeout / 1000;
            tv.tv_usec = (tc->timeout % 1000) * 1000;
            rc = select(tc->fd + 1, &rfds, 0, 0, &tv);
        }
        if (rc == -1) {
            if (errno != EINTR) {
                printf("Error: Failed to select event in thread %d (%s)\n",
                    tc->id, strerror(errno));
            }
            break;
        }
        if (!rc) {
            /* Do some minor work */
            dummy_counter++;
            continue;
        }
        if (FD_ISSET(tc->fd, &rfds)) {
            rc = read(tc->fd, &buf, sizeof(buf));
            if (rc == -1) {
                printf("Error: Failed to read data in thread %d (%s)\n",
                    tc->id, strerror(errno));
                break;
            }
        }
    }

    return 0;
}

static void
signal_handler(int signo)
{
    /* Do nothing */
}
--- main.c ---

--- Makefile ---

PROG=test01

all: $(PROG)

clean:
        rm -f $(PROG) *.o

$(PROG): main.o
        $(CC) -o $(PROG) main.o -lpthread

.c.o:
        $(CC) -c -Wall -Werror -O2 $<

main.o: main.c
--- Makefile ---

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

параметры тестовой машины (RHEL4_x86_32):

$ uname -a
Linux localhost.localdomain 2.6.9-5.ELsmp #1 SMP Wed Jan 5 19:30:39 EST 2005 i686 i686 i386 GNU/Linux

$ cat /proc/meminfo
MemTotal:       254844 kB
MemFree:        181144 kB
Buffers:         13432 kB
Cached:          33368 kB
SwapCached:          0 kB
Active:          31756 kB
Inactive:        21828 kB
HighTotal:           0 kB
HighFree:            0 kB
LowTotal:       254844 kB
LowFree:        181144 kB
SwapTotal:      327672 kB
SwapFree:       327672 kB
Dirty:              36 kB
Writeback:           0 kB
Mapped:          11320 kB
Slab:            15992 kB
Committed_AS:    47832 kB
PageTables:        832 kB
VmallocTotal:   761848 kB
VmallocUsed:      2080 kB
VmallocChunk:   759396 kB
HugePages_Total:     0
HugePages_Free:      0
Hugepagesize:     2048 kB

$ cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 15
model name      : Intel(R) Core(TM)2 CPU          6400  @ 2.13GHz
stepping        : 8
cpu MHz         : 0.000
cache size      : 2048 KB
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 10
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss lm pni ds_cpl
bogomips        : 3137.53

processor       : 1
vendor_id       : GenuineIntel
cpu family      : 6
model           : 15
model name      : Intel(R) Core(TM)2 CPU          6400  @ 2.13GHz
stepping        : 8
cpu MHz         : 0.000
cache size      : 2048 KB
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 10
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss lm pni ds_cpl
bogomips        : 4104.19

...но больше чем три сотни потоков мне создать не дали, аргументируя это ENOMEM... :)

ps: ну и крутится это всё естественно в VMware под WinXP/SP2 что, впрочем, значения не имеет.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

ps: ну и естественно лимитов нет:

$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
file size               (blocks, -f) unlimited
pending signals                 (-i) 1024
max locked memory       (kbytes, -l) 32
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

Вау. Это называется профессиональный подход. Уважаю.

Я прогнал этот тест на своем ноуте: при -n 4000 -t 100 занимает ~10% ЦП,
а при -n 4000 -t 5 вешает систему :)

processor       : 0
vendor_id       : GenuineIntel
cpu family      : 15
model           : 2
model name      : Intel(R) Celeron(R) CPU 2.40GHz
stepping        : 9
cpu MHz         : 2392.299
cache size      : 128 KB
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 2
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe cid xtpr
bogomips        : 4787.22

патч (в основном уменьшает размер стека создаваемых нитей, чтобы создать их побольше)

--- main0       2007-02-05 22:27:09.000000000 +0300
+++ main.c      2007-02-05 22:28:05.000000000 +0300
@@ -14,8 +14,11 @@
     unsigned int timeout, id;
     int fd, is_joinable;
     pthread_t pth;
+    pthread_attr_t att;
 };
 
+int dummy_counter = 0;
+
 int
 main(int argc, char *argv[])
 {
@@ -84,7 +87,9 @@
         tc->id = req_threads - left;
         tc->timeout = timeout;
         tc->fd = fds[0];
-        error = pthread_create(&tc->pth, 0, thread_func, tc);
+       pthread_attr_init(&tc->att);
+       pthread_attr_setstacksize(&tc->att, 32*1024);
+        error = pthread_create(&tc->pth, &tc->att, thread_func, tc);
         if (!error) {
             tc->is_joinable = 1;
             num_threads++;
@@ -121,7 +126,7 @@
     }
     printf("Join %u child threads\n", num_threads);
 
-    printf("Terminate main thread\n");
+    printf("Terminate main thread, counter %d\n", dummy_counter);
 
     return EXIT_SUCCESS;
 }
@@ -131,7 +136,6 @@
 {
     struct thread_cont *tc = arg;
     struct timeval tv;
-    int dummy_counter = 0;
     sigset_t ss;
     fd_set rfds;
     char buf;

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Я прогнал этот тест на своем ноуте: при -n 4000 -t 100 занимает ~10% ЦП, а при -n 4000 -t 5 вешает систему :)

уже позже я прогнал тест на том-же Intel Core2 Duo 6400 но в честно установленном RHEL4_x86_64/SMP с 1Gb RAM, где мне дали создать чуть больше 8000 потоков (исчерпалась виртуалка при 8192-ps aux | wc -l - 2 ?) и при таймауте 10ms загрузка была всё равно порядка 1..2%. кстати, ничего не зависло и по Ctrl-Break честно завершает исполнение примерно за ~1..2sec. естественно все гонялось в голой консоли при отсутствии сторонних сервисов.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от tailgunner

--- cut ---
-        error = pthread_create(&tc->pth, 0, thread_func, tc);
+       pthread_attr_init(&tc->att);
+       pthread_attr_setstacksize(&tc->att, 32*1024);
+        error = pthread_create(&tc->pth, &tc->att, thread_func, tc);
--- cut ---

об этом я конечно же подумал, но оказалось, что мой RHEL4 удивительно
мало знает о setstacksize атрибуте... ;)

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

--- cut ---
об этом я конечно же подумал, но оказалось, что мой RHEL4 удивительно
мало знает о setstacksize атрибуте... ;)
--- cut ---

впрочем, о setstacksize не знает лишь RHEL-овский man в то время как в pthread.h он все-таки объявлен. тогда тест можно сделать немного более универсальным:

--- cut ---
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>

static void *thread_func(void *arg);
static void signal_handler(int signo);

struct thread_cont {
    unsigned int timeout, id;
    int fd, is_joinable;
    pthread_t pth;
};

static unsigned long long dummy_counter;

int
main(int argc, char *argv[])
{
    unsigned int req_threads = 10, timeout = 0, num_threads, left;
    struct thread_cont *threads, *tc;
    int signo, opt, error, fds[2];
    pthread_attr_t attr, *pattr;
    size_t size, stack_size = 0;;
    struct sigaction sa;
    sigset_t ss;

    while ((opt = getopt(argc, argv, "n:s:t:")) != -1) {
        switch (opt) {
        case 'n' :
            if (sscanf(optarg, "%u", &req_threads) != 1 || !req_threads) {
                printf("Error: Invalid number of threads %s\n", optarg);
                return EXIT_FAILURE;
            }
            break;
        case 's' :
            if (sscanf(optarg, "%ld", &stack_size) != 1 ||
                (stack_size && stack_size < PTHREAD_STACK_MIN)) {
                printf("Error: Invalid stack size %s (min %d)\n",
                    optarg, PTHREAD_STACK_MIN);
                return EXIT_FAILURE;
            }
            break;
        case 't' :
            if (sscanf(optarg, "%u", &timeout) != 1) {
                printf("Error: Invalid timeout %s\n", optarg);
                return EXIT_FAILURE;
            }
            break;
        default :
            printf("Error: Unknown command line argument\n");
            return EXIT_FAILURE;
        }
    }

    printf("Options: num threads %u stack size %ld (min %d) timeout %u ms\n",
        req_threads, stack_size, PTHREAD_STACK_MIN, timeout);

    if (pipe(fds) == -1) {
        printf("Error: Failed to create pipe (%s)\n",
            strerror(errno));
        return EXIT_FAILURE;
    }

    size = req_threads * sizeof(*threads);
    threads = malloc(size);
    if (!threads) {
        printf("Error: Failed to alloc threads context (%s)\n",
            strerror(errno));
        return EXIT_FAILURE;
    }
    memset(threads, 0, size);

    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigaction(SIGINT, &sa, 0);
    sigaction(SIGTERM, &sa, 0);
    sigaction(SIGUSR1, &sa, 0);

    sigemptyset(&ss);
    sigaddset(&ss, SIGINT);
    sigaddset(&ss, SIGTERM);
    sigaddset(&ss, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &ss, 0);

    if (!stack_size) {
        pattr = 0;
    } else {
        pthread_attr_init(&attr);
        pthread_attr_setstacksize(&attr, stack_size);
        pattr = &attr;
    }

    num_threads = 0;
    left = req_threads;
    tc = threads;
    while (left) {
        tc->id = req_threads - left;
        tc->timeout = timeout;
        tc->fd = fds[0];
        error = pthread_create(&tc->pth, pattr, thread_func, tc);
        if (!error) {
            tc->is_joinable = 1;
            num_threads++;
        } else {
            printf("Error: Failed to create thread %u (%s)\n",
                tc->id, strerror(error));
        }
        left--, tc++;
    }

    if (pattr) {
        pthread_attr_destroy(pattr);
    }

    printf("Start %u child threads\n", num_threads);
    printf("Suspend main thread waiting for signal\n");
    sigemptyset(&ss);
    sigaddset(&ss, SIGINT);
    sigaddset(&ss, SIGTERM);
    sigwait(&ss, &signo);
    printf("Main thread aborted by signal %d\n", signo);

    num_threads = 0;
    left = req_threads;
    tc = threads;
    while (left) {
        if (tc->is_joinable) {
            pthread_kill(tc->pth, SIGUSR1);
            error = pthread_join(tc->pth, 0);
            if (!error) {
                num_threads++;
            } else {
                printf("Error: Failed to join thread %u (%s)\n",
                    tc->id, strerror(error));
            }
        }
        left--, tc++;
    }
    printf("Join %u child threads, test counter is %llu\n",
        num_threads, dummy_counter);

    printf("Terminate main thread\n");

    return EXIT_SUCCESS;
}

static void *
thread_func(void *arg)
{
    struct thread_cont *tc = arg;
    struct timeval tv;
    sigset_t ss;
    fd_set rfds;
    char buf;
    int rc;

    sigemptyset(&ss);
    sigaddset(&ss, SIGUSR1);
    pthread_sigmask(SIG_UNBLOCK, &ss, 0);

    while (1) {
        FD_ZERO(&rfds);
        FD_SET(tc->fd, &rfds);
        if (!tc->timeout) {
            rc = select(tc->fd + 1, &rfds, 0, 0, 0);
        } else {
            tv.tv_sec = tc->timeout / 1000;
            tv.tv_usec = (tc->timeout % 1000) * 1000;
            rc = select(tc->fd + 1, &rfds, 0, 0, &tv);
        }
        if (rc == -1) {
            if (errno != EINTR) {
                printf("Error: Failed to select event in thread %d (%s)\n",
                    tc->id, strerror(errno));
            }
            break;
        }
        if (!rc) {
            /* Do some minor work */
            dummy_counter++;
            continue;
        }
        if (FD_ISSET(tc->fd, &rfds)) {
            rc = read(tc->fd, &buf, sizeof(buf));
            if (rc == -1) {
                printf("Error: Failed to read data in thread %d (%s)\n",
                    tc->id, strerror(errno));
                break;
            }
        }
    }

    return 0;
}

static void
signal_handler(int signo)
{
    /* Do nothing */
}
--- cut ---

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

кстати, последняя версия теста в указанной ниже конфигурации грузит
родительскую XP в среднем на ~30%. правда top этого не показывает,
мол все по нулям.. ;) так что даже с отнюдь не самыми жесткими настройками (4000 потоков/200ms таймаута) в холостую может тратится весьма прилично...

foo@grey-vm: uname -a
Linux grey-vm.foo.local 2.6.9-5.ELsmp #1 SMP Wed Jan 5 19:30:39 EST 2005 i686 i686 i386 GNU/Linux
foo@grey-vm: cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 15
model           : 2
model name      : Intel(R) Pentium(R) 4 CPU 2.80GHz
stepping        : 8
cpu MHz         : 0.000
cache size      : 512 KB
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 2
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss
bogomips        : 2580.48

processor       : 1
vendor_id       : GenuineIntel
cpu family      : 15
model           : 2
model name      : Intel(R) Pentium(R) 4 CPU 2.80GHz
stepping        : 8
cpu MHz         : 0.000
cache size      : 512 KB
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 2
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss
bogomips        : 65.40

foo@grey-vm: cat /proc/meminfo
MemTotal:       258908 kB
MemFree:         77340 kB
Buffers:         10200 kB
Cached:          72480 kB
SwapCached:          0 kB
Active:          59436 kB
Inactive:        61200 kB
HighTotal:           0 kB
HighFree:            0 kB
LowTotal:       258908 kB
LowFree:         77340 kB
SwapTotal:      327672 kB
SwapFree:       327672 kB
Dirty:             156 kB
Writeback:           0 kB
Mapped:          42620 kB
Slab:            40704 kB
Committed_AS:   171952 kB
PageTables:       1080 kB
VmallocTotal:   753656 kB
VmallocUsed:      2080 kB
VmallocChunk:   751204 kB
HugePages_Total:     0
HugePages_Free:      0
Hugepagesize:     2048 kB

foo@grey-vm: ./test01 -n 4000 -s 32000 -t 200
Options: num threads 4000 stack size 32000 (min 16384) timeout 200 ms
Start 4000 child threads
Suspend main thread waiting for signal
.....

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

ну и напоследок... "что хорошо русскому - немцу смерть" ;)
но думать то желательно и о других системах...

$ uname -a
NetBSD NBSD1 4.99.4 NetBSD 4.99.4 (GENERIC) #0: Wed Nov 22 21:25:30 NOVT 2006  toor@NBSD1:/usr/build/obj/sys/arch/i386/compile/GENERIC i386

$ cat /var/run/dmesg.boot  | grep -e mem -e cpu
total memory = 255 MB
avail memory = 241 MB
cpu0 at mainbus0: (uniprocessor)
cpu0: Intel Celeron (686-class), 1007.08 MHz, id 0x68a
cpu0: features 383fbff<FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR>
cpu0: features 383fbff<PGE,MCA,CMOV,PAT,PSE36,MMX>
cpu0: features 383fbff<FXSR,SSE>
cpu0: I-cache 16 KB 32B/line 4-way, D-cache 16 KB 32B/line 4-way
cpu0: L2 cache 128 KB 32B/line 4-way
cpu0: ITLB 32 4 KB entries 4-way, 2 4 MB entries fully associative
cpu0: DTLB 64 4 KB entries 4-way, 8 4 MB entries 4-way
cpu0: 8 page colors

$ ./test01 -t 100 -n 1000
Options: num threads 1000 stack size 0 (min 16384) timeout 100 ms
Start 1000 child threads
Suspend main thread waiting for signal

$ top
load averages: 17.65,  8.49,  4.54                                     up 34 days,  0:35   14:17:29
68 processes:  1 runnable, 66 sleeping, 1 on processor
CPU states: 34.8% user,  0.0% nice,  9.5% system,  0.0% interrupt, 55.7% idle
Memory: 109M Act, 54M Inact, 16M Wired, 10M Exec, 105M File, 1380K Free
Swap: 547M Total, 547M Free

  PID USERNAME PRI NICE   SIZE   RES STATE      TIME   WCPU    CPU COMMAND
 6332 foo        2    0  2152K   10M RUN      345:22 45.12% 45.12% test01
   10 root      18    0     0K   20M syncer    35:38  0.00%  0.00% [ioflush]
  598 root       2    0  1024K  952K select     2:03  0.00%  0.00% nmbd
    8 root     -18    0     0K   20M lfswrite   1:59  0.00%  0.00% [lfs_writer]
19632 jabberd    2    0  1076K 1420K poll       1:15  0.00%  0.00% c2s

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

masta, а может стоит пересмотреть архитектуру приложения? При большом количестве одновременных долго живущих соединений можно использовать epoll, например, в случае с linux или kqueue в случае с FreeBSD, можно ещё асинхронный ввод-вывод. Так можно избавится от большого количества потоков и даже поднять быстродействие.. Недавно была тема о подобном сервере там была эта ссылка http://www.kegel.com/c10k.html, думаю может помочь.

TaranSergey
()
Ответ на: комментарий от klalafuda

> кстати, последняя версия теста в указанной ниже конфигурации грузит родительскую XP в среднем на ~30%. правда top этого не показывает,

думаю, это артефакт виртуализации

> но думать то желательно и о других системах...

проблемы белых шерифов не волнуют индейца :)

хотя понятно, что, когда нитей за сотню, лучше использовать специальный управляющий канал (или, по крайней мере, тайм-ауты подлиннее).

tailgunner ★★★★★
()
Ответ на: комментарий от TaranSergey

> masta, а может стоит пересмотреть архитектуру приложения? При большом количестве одновременных долго живущих соединений можно использовать epoll, например, в случае с linux или kqueue в случае с FreeBSD, можно ещё асинхронный ввод-вывод. Так можно избавится от большого количества потоков и даже поднять быстродействие.. Недавно была тема о подобном сервере там была эта ссылка http://www.kegel.com/c10k.html, думаю может помочь.

ну конечно же можно, я отнюдь не спорю и не ставлю потоки в противовес демультиплексорам событий.

когда как-то пару раз понадобилось обрабатывать достаточно большое количество (>>1000) одновременных соединений с вялотекущим трафиком, применяли модель threads+libevent(epoll|poll|select|kqueue|devselect) и далее уже настраивали в зависимости от конкретной системы и запросов. пул из N потоков по M демультиплексоров после подгонки соотношения N:M уже на месте вполне себя оправдали.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от tailgunner

> думаю, это артефакт виртуализации

ну и что? работа в виртуальном окружении - вполне жизненная задача, и если прикладное ПО с ней не считается, оно сужает область своей применимости.

> проблемы белых шерифов не волнуют индейца :)

ну это мягко говоря близорукий подход к решению проблемы..

> хотя понятно, что, когда нитей за сотню, лучше использовать специальный управляющий канал (или, по крайней мере, тайм-ауты подлиннее).

подлиннее - понятие количественное, не качественное. для кого-то и 1000ms коротковат, а кто-то и 10min с лёгкой руки поставит..

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

>> думаю, это артефакт виртуализации

> ну и что? работа в виртуальном окружении - вполне жизненная задача

Я смотрю со своей колокольни - и с нее работа виртуальном окружении не просматривается совсем никак.

> подлиннее - понятие количественное, не качественное. для кого-то и 1000ms коротковат, а кто-то и 10min с лёгкой руки поставит..

этой фразы не понял. ну поставит 10мин - после первой проверки завершения приложения получит по рукам, и всё.

tailgunner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.