LINUX.ORG.RU

Приоритеты потоков


0

1

Диспозиция такая: есть два потока, один рисует данные opengl'ем, второй в фоне грузит куски данных. Реализована, соответственно, очередь запросов, защищённая мутексом:

// Главный поток
{
  while (1) {
    pthread_mutex_lock(mutex);
    ...
    отрисовка_данных(dataset);
    ...
    queue.push_back(request);
    ...
    pthread_mutex_unlock(mutex);  /* 1 */
  }
}

// Рабочий поток:
{
  pthread_mutex_lock(mutex);
  while (!die_flag) {
    while (queue.empty())
      pthread_cond_wait(condvar, mutex);
    request = queue.pop_front();
    pthread_mutex_unlock(mutex);

    data = долгая_обработка_куска_данных(request);

    pthread_mutex_lock(mutex);
    dataset.push_back(data);
  }
  pthread_mutex_unlock(mutex);
}

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

На деле же происходит следующая неприятая вещь: первый поток после рендеринга и добавления нескольких запросов в очередь вызывает pthread_mutex_unlock (/* 1 */) и не возвращается из неё 0.1 секунды, за которые второй поток и ухитряется обработать аж десяток кусков данных, соответственно, отображение в главном потоке заметно лагает. Насколько я понимаю, получается так потому что у первого потока, успевшего за время рендеринга пожрать циклов CPU, понижается приоритет, а т.к. второй поток это время спал, он вытесняет первый пока не догонит его по использованию CPU. Вставка sched_yield во второй поток помогает, но сейчас используется два потока, и лаги, хоть и менее заметны, всё ещё присутствуют. Как я понимаю, несколько рабочих потоков будут просто переключаются друг на друга, но не на главный.

Собственно вопрос: как избавиться от лагов? Я так понимаю, приоритеты потокам выставлять не очень хорошо потому что условиях нехватки CPU (т.е. 1 ядро занятое на 100%) поток с меньшим приоритетом будет голодать, а значит данные грузиться не будут. В идеале нужно чтобы главный поток не вытеснялся более чем на 0.01 сек, но при этом чтобы CPU с другими потоками получал поровну.

★★★★★

Пытался читать про pthread_cond_wait(). Тяжеловато воспринимается. Мне показалось, что там надо использовать pthread_cond_signal() для пробуждения. В приведенном коде я вызова этой функции не увидел.

<offtopic>

А ещё я не вижу проверки возвращаемых значений в функциях pthread_XXX. Лень писать, поставь хотя бы заглушки.

</offtopic>

pathfinder ★★★★
()

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

Наивненько. А чего делаешь-то, может, вообще в другую сторону гулять надо?

cnupm
()

>приоритеты потокам выставлять не очень хорошо потому что условиях нехватки CPU (т.е. 1 ядро занятое на 100%) поток с меньшим приоритетом будет голодать

При time-sharing policy нить ни с каким приоритетом не может занять 100% процессорного времени. В зависимости от приоритетов одни будут выполняться больше, а другие меньше.

Murr ★★
()

> долгая операция происходит в потоке никому не мешая

В главном потоке отрисовка происходит под мютексом. Это значит «никому не мешая»?

tailgunner ★★★★★
()

> В приведенном коде я вызова этой функции не увидел.

Эту тут несущественно

А ещё я не вижу проверки возвращаемых значений в функциях pthread_XXX. Лень писать, поставь хотя бы заглушки.

Ага, щас ещё проверок понавставляю в код для примера и комментариев.

Наивненько.

Есть лучшее объяснение?

А чего делаешь-то

То что написал.

может, вообще в другую сторону гулять надо?

И в какую же?

При time-sharing policy нить ни с каким приоритетом не может занять 100% процессорного времени. В зависимости от приоритетов одни будут выполняться больше, а другие меньше.

«Меньше» и есть «голодать».

В главном потоке отрисовка происходит под мютексом. Это значит «никому не мешая»?

Да, это значит никому не мешая, читайте внимательнее.

Так что, идей нет? Что-нибудь в сторону setschedpolicy?

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

>«Меньше» и есть «голодать».

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

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

Раздражение еще никому не помогало получить ответ.

Так что, идей нет?

Попробуй реализовать очередь с ограниченной длиной.

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

Раздражение еще никому не помогало получить ответ.

А он ему и не нужен.

Мне вот интересно, может такую задачу проще реализовать без мьютексов и кондишенов через обмен токенами. Пускай у нас есть 256 токенов. Каждый токен отвечает за определенный блок с данными. Тот у кого токен, может работать с данными в блоке. Передача токенов идет через pipe или unix socket в виде простой записи байта.

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

Но код по идее должен быть простым и прозрачным.

Прозрачнее кода, чем с вычитыванием из нормальной очереди, просто не бывает:

while (1) {
  q.get(req);
  if (req.kind == QUIT)
    break;
  else
    process(req);
}

:)

tailgunner ★★★★★
()

ТС много хочет. Он хочет одновременно реальное и не реальное время. Может от чего-то отказаться? Скажем использовать для критичного потока исполнения sched_setscheduler() с планировщиком SCHED_FIFO, а другой - обычный процесс. Критичный потом сам следит когда надо отдать немного процессорного времени остальной системе, скажем засыпанием на 0.01 сек.

Но все равно, это такой Бугагец. Нужны права администратора и можно завесить всю систему простым зависанием приложения, которое работает с планировщиком SCHED_FIFO.

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

Прозрачнее кода, чем с вычитыванием из нормальной очереди, просто не бывает:

А я не понял. По идее вся сложность должна быть спрятана в методе q.get(req) и методе push, которые должны безопасно доставать из, и заталкивать в очередь в условиях доступа из разных потоков.

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

> По идее вся сложность должна быть спрятана в методе q.get(req) и методе push

put. Она там и спрятана, естественно :)

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

> Как-то вас не понять. Есть ограниченный ресурс. Одной нити не хватает выч. ресурса

Ей хватает. Но она тем не менее, слишком надолго вытесняется.

Попробуй реализовать очередь с ограниченной длиной.

И как это поможет? И один элемент может несколько секунд обрабатываться.

может такую задачу проще реализовать без мьютексов и кондишенов через обмен токенами

ТС много хочет. Он хочет одновременно реальное и не реальное время.

Возможно. Я не до конца представляю как работает шедулинг, иначе бы и не спрашивал.

Скажем использовать для критичного потока исполнения sched_setscheduler() с планировщиком SCHED_FIFO, а другой - обычный процесс. Критичный потом сам следит когда надо отдать немного процессорного времени остальной системе, скажем засыпанием на 0.01 сек.

Вполне. Только, разумеется, без привилегий администратора. Внутри процесса нельзя без прав администратора уравлять шедулингом потоков (pthread_attr_setscheduler?)

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

>Ей хватает. Но она тем не менее, слишком надолго вытесняется.

Это можно отрегулировать обычными (timesharing) приоритетами. Вы пробовали? Поставить -19 рисующей нити и +20 остальным.

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

>Сорри, имелось в виду pthread_attr_setschedpolicy.

Для приоритетов реального времени (SCHED_FIFO/SCHED_RR) потребуются права администратора (CAP_SYS_NICE).

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

> Это можно отрегулировать обычными (timesharing) приоритетами. Вы пробовали? Поставить -19 рисующей нити и +20 остальным.

Пока не пробовал - прощупываю почву. А что будет в описанном выше случае - ядро одно и CPU на всех не хватает. Насколько низкоприоритетные треды будут получать меньше времени чем основной?

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

>> Попробуй реализовать очередь с ограниченной длиной.

И как это поможет?

А тебе ничто не поможет, пока ты не поймешь свою проблему. Потому что:

И один элемент может несколько секунд обрабатываться.

...но совсем недавно:

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

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

> А тебе ничто не поможет

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

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

> Ты мне точно не поможешь, пока не научишься читать

Даже когда научусь :)

извини

Не стоит. Взаимная ошибка, бывает.

tailgunner ★★★★★
()

Дай /proc/config.gz своего ядра посмотреть. Или включи PREEMPT & 1000HZ и посмотри, изменится ли поведение. Если не поможет - ищи realtime расширения ядра. Ты работаешь не с RTOS, гарантировать отклик меньше 10мс тебе линукс («из коробки», да ещё и с небось no preempt & 100hz таймером) не может.

anonymous
()
Ответ на: комментарий от Murr

> SCHED_FIFO/SCHED_RR) потребуются права администратора

строго говоря, нет. есть RLIMIT_RTPRIO (и RLIMIT_RTTIME).

хрен редьки не намного слаще, согласен. но я зануда ;)

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

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

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

>строго говоря, нет. есть RLIMIT_RTPRIO (и RLIMIT_RTTIME).

Так для их увеличения тоже нужен администраторский CAP. А если считать, что нить изначально могла иметь нужный RLIMIT, то с тем же успехом можно считать, что она изначально могла иметь и RT priority.

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

>Мне не нужен отклик 10мс, мне нужно чтобы основной поток не спал слишком долго. Ковыряние в ядре не надо предлагать.

Чтобы не спать слишком долго, нужно спать ложиться пораньше, а не когда с уже с ног валишься. Я серьезно. Главный поток - трудоголик, сам не засыпает. А ты хочешь, чтобы ОС угадала твои желания, когда укладывать его спать.

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

Главный поток насильно спит каждый кадр usleep'ом и CPU практически не ест.

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

>Насколько низкоприоритетные треды будут получать меньше времени чем основной?

2.6.18 отображает приоритеты -20..19 для SCHED_OTHER на временные интервалы 5..800 мс (чем меньше значение приоритета, тем больше интервал, 0 - 100 мс).

При определенных обстоятельствах (например, когда нить долго спит), правда, следование A->B->A->B->... может нарушаться. Не хочу делать более конкретные утверждения, а то idle может найти кучу ошибок и неточностей. :)

Murr ★★
()

man «Проблема производитель-потребитель», а еще «полоса пропускания». В описании твоей диспозиции существенно не хватает начальных условий: количество ядер проца/количество аппаратных потоков/поддержка процом гипертрединга и т.д.

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

> В описании твоей диспозиции существенно не хватает начальных условий: количество ядер проца/количество аппаратных потоков/поддержка процом гипертрединга и т.д.

Э-э-э, с каких это пор софт пишут под количество ядер/потоков/гипертрединг? Разумеется, нет таких условий.

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

> Проблема производитель-потребитель

Нет.

а еще «полоса пропускания»

Ссылку можно? Слишком общий термин для гугления.

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

> Так для их увеличения тоже нужен администраторский CAP

так я ж поэтому и написал про хрен.

А если считать, что нить изначально могла иметь нужный RLIMIT


iirc, в каких-то дистрибутивах так и есть. или было.
потом, это немного безопаснее, чем CAP_SYS_NICE, тк
есть еще RLIMIT_RTTIME.

правда, теперь в фаворе rt group scheduling, а ему
начхать на эти limits.

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

>Э-э-э, с каких это пор софт пишут под количество ядер/потоков/гипертрединг? Разумеется, нет таких условий.

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

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

Это всё я и без вас знаю. Вопрос был как решать конкретную задачу чтобы работало везде.

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

>правда, теперь в фаворе rt group scheduling, а ему

начхать на эти limits.

В фаворе - это ты хорошо сказал. :) Дай бог дожить до тех времен, когда народ начнет активно съезжать с RHEL5. ;)

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

> Мне не нужен отклик 10мс, мне нужно чтобы основной поток не спал слишком долго. Ковыряние в ядре не надо предлагать.

Хм, я между строк прочитав твой первый пост получил картину, что ты вроде как всё делаешь правильно, но один из потоков шедулится слишком поздно.

Приведённые тобой задержки порядка 10мс не могут не натолкнуть на мысль посмотреть конфиг ядра.

Сделай «zegrep '(HZ|PREE)' /proc/config.gz» и покажи результат.

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

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

Я советую в первую очередь включить CONFIG_PREEMPT (forced preemption), CONFIG_HZ_1000 & CONFIG_HZ=1000 и перетестировать (если оно не включенно).

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

> при появлении NO_HZ и включении его всё было плохо

Там была масса побочных проблем, начиная с банальных ошибок и упущений в ядре, до глюков в пользовательском коде. Например когда вызывался usleep(1), вместо sched_yield().

Потом кто-то экспериментировал с костылями на usleep(1) и местами это глючило (только не знаю насколько это попадало в релизные версии).

Потом еще Инго N-надцать раз все докручивал, периодически уходя то в свою RT-ветку, то «возвращался» назад.

Сейчас dyn-тики работают в 99.9%.

ly
()
Ответ на: комментарий от anonymous

> В чём адванс этих dynamic ticks?

1. Можно экономить на питании системы, CPU может спать в точности до нужного момента.

2. Можно обслуживать таймауты много меньше 1 мс.

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

> 2. Можно обслуживать таймауты много меньше 1 мс.

Это делается с помощью hrtimers, dynamic ticks здесь непричём.

1. Можно экономить на питании системы, CPU может спать в точности до нужного момента.

Вот, это более похоже на правду. Но экономичностью можно пожертвовать, если требуется RT, так?

anonymous
()
Ответ на: комментарий от ly

> 2. Можно обслуживать таймауты много меньше 1 мс.

Это делается с помощью hrtimers, dynamic ticks здесь непричём.

1. Можно экономить на питании системы, CPU может спать в точности до нужного момента.

Вот, это более похоже на правду. Но экономичностью можно пожертвовать, если требуется RT, так?

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

2. Можно обслуживать таймауты много меньше 1 мс.

Это делается с помощью hrtimers, dynamic ticks здесь непричём.

dynamic ticks
-------------

Dynamic ticks are the logical consequence of the hrtimer based periodic tick
replacement (sched_tick). The functionality of the sched_tick hrtimer is
extended by three functions:

- hrtimer_stop_sched_tick
- hrtimer_restart_sched_tick
- hrtimer_update_jiffies

hrtimer_stop_sched_tick() is called when a CPU goes into idle state. The code
evaluates the next scheduled timer event (from both hrtimers and the timer
wheel) and in case that the next event is further away than the next tick it
reprograms the sched_tick to this future event, to allow longer idle sleeps
without worthless interruption by the periodic tick...

...The implementation leaves room for further development like full tickless
systems, where the time slice is controlled by the scheduler, variable
frequency profiling, and a complete removal of jiffies in the future...


	  Thomas, Ingo

What's in 2.6.21 is, thus, not a full dynamic tick implementation. Eliminating the tick during idle times is a good step forward, but there is value in getting rid of the tick while the system is running as well - especially on virtualized systems which may be sharing a host with quite a few other clients. The dynamic tick documentation file suggests that the developers have this goal in mind:

    The implementation leaves room for further development like full
    tickless systems, where the time slice is controlled by the scheduler,
    variable frequency profiling, and a complete removal of jiffies in the future.

So expect some interesting work in the future - the removal of jiffies alone has a number of interesting implications.

Короче, не задействовано / не работает в полном объеме из-за «usleep(1)/select(.., 1)/poll(..., 1) вместо sched_yield()» в тоннах кода. В таких случаях полностью dyntick-ядро начинает будить процесс «не раз в в тик», а каждую микросекунду (или как там запрошено). И вместо минимального ожидания, получается busy-loop.

Но не считается проблемой, поскольку кому нужно может заюзать hrtimer.

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

> из-за «usleep(1)/select(.., 1)/poll(..., 1) вместо sched_yield()» в тоннах кода.

Уж несколько лет (со времен появления O(1) scheduler) не рекомендовалось пользоваться sched_yield.

В таких случаях полностью dyntick-ядро начинает будить процесс «не раз в в тик», а каждую микросекунду (или как там запрошено).

Выглядит, мягко говоря, неправдоподобно.

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

>> из-за «usleep(1)/select(.., 1)/poll(..., 1) вместо sched_yield()» в тоннах кода.

Уж несколько лет (со времен появления O(1) scheduler) не рекомендовалось пользоваться sched_yield.

В таких случаях полностью dyntick-ядро начинает будить процесс «не раз в в тик», а каждую микросекунду (или как там запрошено).

Выглядит, мягко говоря, неправдоподобно.

Я про другое говорю - когда вызывают usleep/select/poll(1) чтобы чуть-чуть подождать (в цикле), предполагая что планировщик запустит задачу на следующем тике, а не через 1 us.

Если ядро будет полностью верным dynitick-ам, то процесс будет «засыпать» на 1 us, и получиться а-ля busy-loop.

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

> процесс будет «засыпать» на 1 us, и получиться а-ля busy-loop.

я лично с этим сталкивался ;) те, у меня был bugreport
про «kernel regression» из-за которого приложение потребляло
больше CPU.

разобрался-то я довольно быстро, сложно было обьяснить,
что теперь nanosleep(чуть-чуть) работает точнее, а фиксить
надо user-space.

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

> Если ядро будет полностью верным dynitick-ам, то процесс будет «засыпать» на 1 us, и получиться а-ля busy-loop.

Вы неправильно понимаете, что такое dyntick и путаете с high resolution timers. Перечитайте полностью - это расширение hrtimers, которое всего навсего выключает генерацию таймер-евента, если нам «не нужно просыпаться» для обработки чего-либо:

code evaluates the next scheduled timer event (from both hrtimers and the timer wheel) and ***in case that the next event is further away than the next tick*** it reprograms the sched_tick to this future event, ***to allow longer idle sleeps without worthless interruption*** by the periodic tick

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

> Вы неправильно понимаете, что такое dyntick и путаете

с high resolution timers.


я, кстати, тоже спутал. я думал по dyntick понимается
hrtimer_reprogram/etc.

а так да, CONFIG_NO_HZ не зависит от HIGH_RES_TIMERS,
я даже не знал что «tickless mode» называют dynticks.

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

Вы неправильно понимаете, что такое dyntick и путаете с high resolution timers. Перечитайте полностью - это расширение hrtimers, которое всего навсего выключает генерацию таймер-евента, если нам «не нужно просыпаться» для обработки чего-либо.

Дак я про то и говорил, что после экспериментов Инго subj превратился в прореживатель тиков. Все остальное обламывается на «usleep(1)», либо повсеместном поклонении jiffies в ядре.

The implementation leaves room for further development like full tickless systems, where the time slice is controlled by the scheduler, variable frequency profiling, and a complete removal of jiffies in the future.

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