Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: WinAPI и системное программирование > Многопоточное программирование


Автор: ilyalyu 1.4.2007, 14:16
Эти вопросы относятся по-видимому не конкретно к c++, а к многопоточному программированию в целом.

1. Вопрос первый. При вызове функций место для локальных переменных выделяется в стеке. Допустим, внутри двух разных потоков произошел вызов функций. Правильно ли, что стек будет выглядеть следующим образом:

| Начало стека (занято)
| Место под локальные переменные потока 1
| Место под локальные переменные потока 2
| Конец стека (свободно)

Если так, то правильно ли, что выход из функции, вызванной внутри потока 1, сможет произойти только после завершения функции, вызванной внутри потока 2? В противном случае получится следующее:

| Начало стека (занято)
| Середина стека (свободно - !!!)
| Место под локальные переменные потока 2
| Конец стека (свободно)

И более глобальный вопрос - нужно ли вообще думать обо всем написанном выше или все эти проблемы решаются автоматически.

2. Я использую в программе критические секции. Для того чтобы исключить недопонимание, отмечу, что число критических секций НЕ ЕСТЬ число входов/выходов из критической секции (т.е. критическая секция может быть одна, а число входов/выходов много). Так вот вопрос в следующем. (1) Насколько критичным для скорости выполнения программы является использование большого количества критических секций (допустим порядка 1000). (2) Тот же вопрос, но уже относительно входов/выходов из критическую секцию. Например, насколько замедлится программа, если входы/выходы будут происходить через каждые 10 строчек (при этом считаем, что медленных операций, типа чтения/записи в файл, там нет).

Автор: Daevaorn 1.4.2007, 14:27
у каждого потока свой стек

Автор: ilyalyu 1.4.2007, 14:42
Цитата(Daevaorn @ 1.4.2007,  14:27)
у каждого потока свой стек

Ясно. С первым вопросом разобрались. Спасибо!!!

Кстати, забыл спросить. А насколько эффективно использование большого количества потоков. Например, порядка тысячи. Например, не окжется ли, что переключение между потоками будет занимать 90% процессорного времени и т.д.

Автор: dumb 1.4.2007, 17:29
Цитата(ilyalyu @  1.4.2007,  14:42 Найти цитируемый пост)
А насколько эффективно использование большого количества потоков. Например, порядка тысячи.

в общем случае - очень неэффективно. особенно "порядка тысячи".

Автор: W4FhLF 1.4.2007, 17:59
Цитата(ilyalyu @  1.4.2007,  14:16 Найти цитируемый пост)
2. Я использую в программе критические секции. Для того чтобы исключить недопонимание, отмечу, что число критических секций НЕ ЕСТЬ число входов/выходов из критической секции (т.е. критическая секция может быть одна, а число входов/выходов много). Так вот вопрос в следующем. (1) Насколько критичным для скорости выполнения программы является использование большого количества критических секций (допустим порядка 1000). (2) Тот же вопрос, но уже относительно входов/выходов из критическую секцию. Например, насколько замедлится программа, если входы/выходы будут происходить через каждые 10 строчек (при этом считаем, что медленных операций, типа чтения/записи в файл, там нет).


Ну как сказать, смотря, что ты вкладываешь в понятия "быстро" и "медленно". Критические секции юзермодный механизм синхронизации, т.е. переходов между уровня привелегий не будет, это уже плюс. А в остальном ну выделяется память системой, заполняется структура, далее сравнивается и в конце-концов освобждается. Сам прикидывай насколько это быстро. 
А какие операции ты выполняешь в секции? Если тебе нужно выполнить только чтение/запись в память, то может обойдёшься стандартными Inetlocked-функциями?

Добавлено через 2 минуты и 18 секунд
Цитата(ilyalyu @  1.4.2007,  14:42 Найти цитируемый пост)
Кстати, забыл спросить. А насколько эффективно использование большого количества потоков. Например, порядка тысячи. Например, не окжется ли, что переключение между потоками будет занимать 90% процессорного времени и т.д.


Если ты выполняешь синхронные действия, т.е. действия, которые не заставляют ждать ответа от сторонних приложений или, к примеру, из сокета, то даже 2 потока не дадут тебе прироста больше 10%, а уж 1000 вообще сведут на 0 и даже угонят в минус, твою оптимизацию.

Автор: ilyalyu 1.4.2007, 18:47
dumb, W4FhLF, спасибо за коментарии.

Для того чтобы всем облегчить жизнь, напишу, для чего я использую потоки. Я пишу чат-сервер с использованием сокетов. Соответственно, большинство потоков просто ожидают данных из сокета. Весь вопрос в том, что происходит в это время. Я в меру своей необразованности представляю это так: переодически происходит переключение между разными потоками и они проверяют, не поступило ли данных. Соответственно, время расходуется на переключение и проверку сокетов. Вот вопрос, если брать в расчет только эти операции, то сколько потоков одновременно можно потянуть. Дело в том, что я уже написал чат-сервер на PHP без потоков, но он уже при 100 юзерах онлайн грузит процессор на 30% и более. Мне надо, чтобы сервер нормально работал при 1000+ юзерах онлайн.

Вопрос насчет критических секций разрешился с помощью тестирования. 2,5 миллиона входов и выходов за одну секунду на 667мгц. Думаю, мне этого вполне достаточно smile

Автор: ilyalyu 1.4.2007, 19:27
Теперь протестировал работу большого количества потоков. Код приблизительно такой:

Код

function g(p: Pointer): Longint;
begin
  while true do begin
    Sleep(1000);
    EnterCriticalSection(__CSIOScreen);
    LeaveCriticalSection(__CSIOScreen);
  end
end;

for i := 1 to 10000 do begin BeginThread(g);


Внутри потоков ничего не происходит, они просто "просыпаются" раз в секунду. Оказалось, что на создание 10 тысяч потоков уходит 30 секунд. То есть 300 потоков в секунду (что-то долго...). После этого программа потребляет 2% от 677мгц. То есть одновременно могут функционировать порядка 500 тысяч потоков. Памяти все это занимает 90мб, то есть 9кб на поток.

Автор: Бонифаций 1.4.2007, 20:20
  • По умолчанию каждый трид (в линуксе) выделяет 2М памяти под стек. То есть на 1000 клиентов вам понадобится около 2 гиг памяти только на то чтобы ваша программа как-то дышала.. Если у вас физической памяти меньше, то наличие такого количества тридов приведет к дикому свапированию памяти. 
  •  По умолчанию в glibc внутренние структуры заточены под максимум 1024 трида. Если у вас их будет больше, необходима перекомпиляция glibc. Это бооольшая жывопырка
  •  Обычно такие задачи с большим количеством вялых клиентов решаются при помощи асинхронной работы сокетов. Смотрите функции select и poll

Автор: ilyalyu 1.4.2007, 22:18
Бонифаций, спасибо за коментарий. Посмотрел по интернету, что пишут про поддержку потоков и пришел к предварительному выводу, что под Windows лучше использовать потоки (и это вроде бы подтверждается моими тестами - 10000 потоков потребляют всего 1-2% процессорного времени), а под Linux лучше использовать операцию Select.

Вопрос - насколько этот вывод правильный?

Что касается размера стека, то его можно задавать в функции BeginThread. А разве под Linux стек не растет динамически, как под Windows? Вот уж не ожидал. На Windows обычно всех собак вешают, а выходит, что и поддержка потоков у него лучше, и о размере стека в отличие от Linux не приходится беспокоиться...

Автор: Бонифаций 1.4.2007, 22:31
PS PS. то что я присал относится к linuxthread. В NPTL другая кухня. А для чатов лучше использовать select в любом случае.

Автор: ilyalyu 1.4.2007, 22:33
Я пишу на Free Pascal. Пока я там нашел select только под Linux. Под Windows его просто нет или я пока не нашел. Но я еще поищу. Спасибо.

Автор: Void 1.4.2007, 22:45
ilyalyu, создал 10000 потоков? Отлично, а теперь посмотри в диспетчере задач, сколько потоков у твоего процесса. Окажется чуть более двух тысяч. Это предел для процесса: стеки потоков занимают всё адресное пространство. Своп не начинается, потому что память не закоммичена.
Лучше не использовать модель «один клиент — один поток», это ж не Эрланг всё-таки.
В Windows для таких вещей рекомендуется использовать пулы потоков.

Автор: ilyalyu 1.4.2007, 23:14
В диспечере задач я такой информации не нашел., но при проверке с помощью приведенного ниже кода выяснилось, что создается порядка 8000 потоков, т.е. действительно есть ограничение. 

Код

function g(p: Pointer): Longint;
begin
  EnterCriticalSection(CS);
  Count := Count+1;
  LeaveCriticalSection(CS);
  while True do Sleep(1000);
end;

for i := 1 to 10000 do BeginThread(g);
writeln(Count);


Вашу идею я понял. Можно проверять сокеты на наличие данных, а затем уже обрабатывать их внутри нескольких потоков. Я согласен, что это наиболее оптимальное решение. Вот только найду, где select под windows... Точнее я уже нашел - в юните winsock. Осталось теперь только help к ней найти smile

Автор: dumb 2.4.2007, 02:18
Цитата(ilyalyu @  1.4.2007,  23:14 Найти цитируемый пост)
for i := 1 to 10000 do BeginThread(g);
writeln(Count);

все твои "замеры" некорректны(про некоторые моменты уже сказали) или просто содержат ошибку - как процитированный кусок.
настоятельно советую прочитать несколько статей про многопоточность.
начать можно с http://forum.vingrad.ru/topic-60076/view-all.html.

Цитата(ilyalyu @  1.4.2007,  19:27 Найти цитируемый пост)
То есть одновременно могут функционировать порядка 500 тысяч потоков.

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

Автор: Pulse69 2.4.2007, 09:02
Цитата(ilyalyu @  1.4.2007,  19:27 Найти цитируемый пост)
То есть одновременно могут функционировать порядка 500 тысяч потоков.

Очень вряд ли что такое число потков в реальной работе не подвесит Вашу систему.
Модель "один клиент - один поток" является самой неэффективной. Вы вряд ли сможете 
её использовать если число подключенных клиентов превысит 500-600.
Единственный способ в Windows обрабатывать тысячи клиентов - это IO Completion Ports.
По разным оценкам, приложение на основе IOCP могут обсуживать до 32000 клиентов.
Если Вам нужно действительно эффективное приложение, стоит всерьёз рассмотреть
возможность использования IOCP. Поверьте, разница действительно впечатляет.

Для Winsock начать можно с этой статьи.
http://msdn.microsoft.com/msdnmag/issues/1000/Winsock/

Автор: MAKCim 2.4.2007, 09:28
Цитата(Бонифаций @  1.4.2007,  22:31 Найти цитируемый пост)
PS PS. то что я присал относится к linuxthread

откуда сведения про 2Мб?

Автор: Бонифаций 2.4.2007, 10:54
1) исходники glibc (linuxthread/internals.h)
2) Много раз во всякой документации встречалось. linuxthread default stack size 2M, windows - 1M freebsd - 64k

Добавлено через 3 минуты и 32 секунды
для интереса в гугле поискал пример такой документации. Linuxthread faq:

D.10: My application needs to create thousands of threads, or maybe even more. Can I do this with LinuxThreads?
No. You're going to run into several hard limits:

    * Each thread, from the kernel's standpoint, is one process. Stock Linux kernels (version 2.2 and earlier) are limited to at most 512 processes for the super-user, and half this number for regular users. This can be changed by changing NR_TASKS in include/linux/tasks.h and recompiling the kernel. On the x86 processors at least, architectural constraints seem to limit NR_TASKS to 4090 at most. (It seems that 2.4 kernels have higher limits, though.)
    * LinuxThreads contains a table of all active threads. This table has room for 1024 threads at most. To increase this limit, you must change PTHREAD_THREADS_MAX in the LinuxThreads/glibc sources and recompile.
    * By default, each thread reserves 2M of virtual memory space for its stack. This space is just reserved; actual memory is allocated for the stack on demand. But still, on a 32-bit processor, the total virtual memory space available for the stacks is on the order of 1G, meaning that more than 500 threads will have a hard time fitting in. You can overcome this limitation by moving to a 64-bit platform, or by allocating smaller stacks yourself using the setstackaddr attribute.
    * Finally, the Linux kernel contains many algorithms that run in time proportional to the number of process table entries. Increasing this number drastically will slow down the kernel operations noticeably. 

Автор: ilyalyu 2.4.2007, 12:02
всем спасибо. на основании приведенных выше коментариев я решил пересмотреть концепцию программы. действовать будут так:

1 поток: в режиме accept, обрабатывает новые соединения.
1 поток: в режиме select, ожидает данные из сокетов.
8-32 потоков: на подхвате, читают и обрабатывают данные из сокетов.

функцию select я нашел. под windows она находится в юните winsock, под linux в юните oldlinux. так что больших проблем с совместимостью, надеюсь, не будет.

за ссылки также спасибо.

по поводу стека повторяю, что размер стека можно указать в качестве параметра при создании потока (по крайней мере на Free Pascal).

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)