эффективная реализация thread pool
От: barney  
Дата: 17.05.18 05:51
Оценка:
Привет желающим присоединиться к исследованию Thread Pool

Насколько я понимаю, Thread Pool это вариация на тему multiple producers/multiple consumers.
Схематически, должно быть так:
0. Нужна конкурентная queue очередь
1. Делается N worker threads — например, N = количеству аппаратных параллельных ядер
2. В каждом worker запускается "выгребатель" работы из очереди
3. Функция dispatch принимает кусочки работы (например std::function) и ложит их в очередь

С точки зрения поставщика "кусочков работы" АПИ будет выглядеть например так:

void doer() {
Work work1 = [](){} /// Work обертка может быть std::function
Pool::dispatch(work1);
}

Эффективно реализовать очередь можно с помощью condition variable
Т.е auto work = queue.pop(); заблокируется до появления новых данных в очереди

std::function<void(void)> Queue::pop() {
cond.wait();
...
}

void Queue::push(std::function<void(void) f) {
...
cond.signal();
}


Т.е каждый поток может сделать push() и каждый же поток — заблокироваться и заснуть на pop() до поступления работы.

Вопросы, которые не очень ясны.

1) Какой механизм меж-потоковой синхронизации?
Например я хочу гарантировать очередь исполнения моих ворков

допустим, из разных потоков я диспатчу:
dispatch(work1) | dispatch(work2)

я хочу чтобы work1 гарантированно исполнилась перед work2
при этом, чтобы потоки не простаивали
для этого work1 нужно не просто разместить в очереди до work2
а и гарантировать что поток, исполняющий work1 закончится до того, как другой поток возьмется за work2
Возможно тут нужны некие барьеры

2) Как эффективно передается Work между потоками?
Я так понимаю, что все thread используют общую память т.е. нет никакой нужды ничего никуда копировать? Или как?
Будет ли эффективной передача std::function по значению?

3) Как вообще делаются барьеры?
т.е чтобы был некий dispatch_barrier() который заставит все задиспатченные ранее Work выполниться до него гарантированно,
при этом сохранив возможность после него делать новые dispatch() просто добавляя новые Work в очередь
Re: эффективная реализация thread pool
От: reversecode google
Дата: 17.05.18 06:41
Оценка: 1 (1) +1
это все уже есть в boost::asio
изучите как он устроен что бы не изобретать велосипеды
или как минимум что бы знать как строятся такие решения
Re: эффективная реализация thread pool
От: lpd Черногория  
Дата: 17.05.18 06:49
Оценка:
Здравствуйте, barney, Вы писали:

B>для этого work1 нужно не просто разместить в очереди до work2

B>а и гарантировать что поток, исполняющий work1 закончится до того, как другой поток возьмется за work2
B>Возможно тут нужны некие барьеры

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

B>3) Как вообще делаются барьеры?

B>т.е чтобы был некий dispatch_barrier() который заставит все задиспатченные ранее Work выполниться до него гарантированно,
B>при этом сохранив возможность после него делать новые dispatch() просто добавляя новые Work в очередь

Барьер это отдельный примитив синхронизации, который используется, например, в MPI на кластерах.

B>2) Как эффективно передается Work между потоками?

B>Я так понимаю, что все thread используют общую память т.е. нет никакой нужды ничего никуда копировать? Или как?
Память потоков общая, хотя может быть еще свой TLS(Thread Local Storage).
B>Будет ли эффективной передача std::function по значению?
Здесь это заметным образом не повлияет не на что.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 17.05.2018 6:51 lpd . Предыдущая версия .
Re: эффективная реализация thread pool
От: reversecode google
Дата: 17.05.18 07:15
Оценка: -1
есть еще у яндекса такое https://www.youtube.com/watch?v=cEbGlHNcIl4
или на почти тоже что у яндекса только на паблике
https://github.com/rethinkdb/rethinkdb/tree/next/src/concurrency
но вы ж разбираться все равно не будете
Re[2]: эффективная реализация thread pool
От: barney  
Дата: 17.05.18 07:38
Оценка:
R>есть еще у яндекса такое https://www.youtube.com/watch?v=cEbGlHNcIl4

угу, любопытная лекция Жестилевского,

R>или на почти тоже что у яндекса только на паблике

R>https://github.com/rethinkdb/rethinkdb/tree/next/src/concurrency
R>но вы ж разбираться все равно не будете

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

тут интересно понять базовые принципы для начала
а не закопаться в промышленной библиотеке на тысячи строк сложного кода
Re[3]: эффективная реализация thread pool
От: reversecode google
Дата: 17.05.18 07:47
Оценка:
причем здесь выгорели ?
все уже давно придумано (с)
остается лишь изучить то что уже есть, а вы не хотите
если не можете читать исходники какого бы размера они не были, наверное программирование не для вас ... я так думаю
тот конкуренси по ссылкам или тот же asio не самые большие проекты, если вы в них не сможете разобраться ....
Re[4]: эффективная реализация thread pool
От: barney  
Дата: 17.05.18 07:53
Оценка:
R>все уже давно придумано (с)

заучивание чужого — не научит мыслить, лишь мыслям.
даст готовое решение, а не навык решать.
чтобы по настоящему разобраться в теме — важно пройти путь, который прошел автор "с нуля"
понять, какие проблемы он решал, какие фундаментальные причины лежали в основе принятых решений.
ну а если привычнее потреблять готовое — то в чем проблема? пройдите мимо
зачем провоцировать меня на офтопы? зачем засирать ветку философскими вопросами?
эта ветка для таких же как я — строителей велосипедов)
тех, кто любит учиться, разбирая и изобретая с нуля. а не заучивая чужое
Re[5]: эффективная реализация thread pool
От: XuMuK Россия  
Дата: 17.05.18 09:01
Оценка:
Здравствуйте, barney, Вы писали:

ну и кто здесь троллит?

B>заучивание чужого — не научит мыслить, лишь мыслям. даст готовое решение, а не навык решать.

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

B>зачем провоцировать меня на офтопы? зачем засирать ветку философскими вопросами?

потому что велосипедить с нуля тут никому не интересно.
вопрос по теме должен звучать как-то так: "использую тредпул из boost::asio, не устраивает тем-то и тем-то, попробовал исправить то, что не устраивает, но получилось не очень. подскажите что делать?"
Re[6]: эффективная реализация thread pool
От: barney  
Дата: 17.05.18 09:19
Оценка: +1 -2
XMK>потому что велосипедить с нуля тут никому не интересно.
лол, очередной пояснятель "за всех")
пшел вон ) ветка для тех, кому интересно.
не относишь себя к таковым? в чем проблема?
пройди мимо.
это клуб любителей самодельных велосипедов)
что за синдром "защемленного яичка"?)
надо обязательно прийти и накакать?))
Re: эффективная реализация thread pool
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 10:12
Оценка:
Здравствуйте, barney, Вы писали:

B>1) Какой механизм меж-потоковой синхронизации?


В общем случае код воркера выглядит где-то так:

  queue_mutex.lock();
  for(;;) {
    if (shutdown_flag) {
      break;
    }
    if (!queue.empty()) {
      auto work = queue.pop_front(); // C++ - front, но потом pop_front, чтобы удалить
      queue_mutex.unlock();
      work();
      queue_mutex.lock();
    } else {
      queue_cv.wait(&queue_mutex);
    }
  }
  queue_mutex.unlock();
}


На входе в цикл воркера лочишь мьютекс и проверяешь очередь. Если она пуста, становишься в wait. wait() атомарно становится в очередь на сигнал и разлочивает мьютекс. Как именно ей указать мьютекс — зависит от API; в одних конкретный мьютекс это аргумент метода wait(), в других надо установить проперть у CV.
Дальше оно ждёт сигнала (одного или на всех), и выходит из wait(), залочивая тот же мьютекс.
Проверяешь снова, есть ли работа или приказ на shutdown.
Во время работы мьютекс надо явно отпустить, но после того, как работа вынута из очереди. Потом снова захватить.
Учти, что любой CV может получать stray interrupts, то есть из wait() вышли, но соответствующего signal()/notify() или signal_all()/broadcast() не было. Это норма, тогда надо без жалоб уйти на следующий цикл сна.

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

Укладка в очередь для такого подхода:

  queue_mutex.lock();
  queue.push_back(new_work);
  queue_cv.signal();
  queue_mutex.unlock();


B>Например я хочу гарантировать очередь исполнения моих ворков


B>допустим, из разных потоков я диспатчу:

B>dispatch(work1) | dispatch(work2)

B>я хочу чтобы work1 гарантированно исполнилась перед work2

B>при этом, чтобы потоки не простаивали

А вот тут никак, кроме как что диспетчер следит, что work2 требует завершения work1, и до этого завершения держит work2 в отдельной очереди, которую не кидает целевым воркерам.
Как именно — от темы thread pool никак не зависит, это тема структуры данных типа "граф задач".
Назовёшь это барьером или нет — несущественно.

B>2) Как эффективно передается Work между потоками?

B>Я так понимаю, что все thread используют общую память т.е. нет никакой нужды ничего никуда копировать? Или как?

Общая память. Но каждая передача между ядрами будет требовать сотню тактов. Поэтому часто их делать нельзя.

B>Будет ли эффективной передача std::function по значению?


Вполне.
The God is real, unless declared integer.
Отредактировано 17.05.2018 10:23 netch80 . Предыдущая версия . Еще …
Отредактировано 17.05.2018 10:21 netch80 . Предыдущая версия .
Re[2]: эффективная реализация thread pool
От: barney  
Дата: 17.05.18 10:41
Оценка:
Привет, netch80
О, спасибо за содержательный ответ, любопытно!

N>В общем случае код воркера выглядит где-то так:


N>
N>  queue_mutex.lock();
N>  for(;;) {
N>    if (shutdown_flag) {
N>      break;
N>    }
N>    if (!queue.empty()) {
N>      auto work = queue.pop_front(); // C++ - front, но потом pop_front, чтобы удалить
N>      queue_mutex.unlock();
N>      work();
N>      queue_mutex.lock();
N>    } else {
N>      queue_cv.wait(&queue_mutex);
N>    }
N>  }
N>  queue_mutex.unlock();
N>}
N>


Т.е все воркеры сразу блокируются на мьютексе очереди.
Затем один из них выгребает (непустую) очередь, убирает мьютекс и сразу же запускает work().
В это время какой то другой воркер ухватит мьютекс и пойдет запускать свою работу.

Только не понял, зачем мьютекс лочится вконце после work? work(); queue_mutex.lock();
Только для симметрии чтобы компенсировать общий unlock? Я так понимаю можно просто сделать брейк без блокировки мьютекса, это будет чуть эффективнее, так?

Второй момент, возможно ли внутри воркера (вызванного из work()) вложенное обращение к диспетчеру же с какой то новой работой?
ну например вот так:

Dispatcher::dispatch([](){ 
    do_worker1();                  // эта лямбда выполняется внутри queue_mutex.unlock(); work(); queue_mutex.lock();
    Dispatcher::dispatch([]()      // здесь диспетчер попытается захватить мьютекс снова, но если он захвачен другим потоком - это безопасно ли? нет ли deadlock?
    { 
        some_more_work();
    });
});


N>На входе в цикл воркера лочишь мьютекс и проверяешь очередь. Если она пуста, становишься в wait. wait() атомарно становится в очередь на сигнал и разлочивает мьютекс. Как именно ей указать мьютекс — зависит от API; в одних конкретный мьютекс это аргумент метода wait(), в других надо установить проперть у CV.

N>Дальше оно ждёт сигнала (одного или на всех), и выходит из wait(), залочивая тот же мьютекс.
N>Проверяешь снова, есть ли работа или приказ на shutdown.
N>Во время работы мьютекс надо явно отпустить, но после того, как работа вынута из очереди. Потом снова захватить.
N>Учти, что любой CV может получать stray interrupts, то есть из wait() вышли, но соответствующего signal()/notify() или signal_all()/broadcast() не было. Это норма, тогда надо без жалоб уйти на следующий цикл сна.

N>В общем всё. Можно обвесить счётчиками и т.п., но не принципиально.

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

N>Укладка в очередь для такого подхода:


N>
N>  queue_mutex.lock();
N>  queue.push_back(new_work);
N>  queue_cv.signal();
N>  queue_mutex.unlock();
N>


B>>dispatch(work1) | dispatch(work2)

B>>я хочу чтобы work1 гарантированно исполнилась перед work2
B>>при этом, чтобы потоки не простаивали

N>А вот тут никак, кроме как что диспетчер следит, что work2 требует завершения work1, и до этого завершения держит work2 в отдельной очереди, которую не кидает целевым воркерам.

N>Как именно — от темы thread pool никак не зависит, это тема структуры данных типа "граф задач".
N>Назовёшь это барьером или нет — несущественно.

Понятно, тут можно по сути вручную прикрутить любую кастомную логику.
Например помечать воркеры тегами — или ставить как то зависимости.
Можно даже приоритеты навернуть, чтобы при выборке работы — сначала шерстили всю очередь на предмет приоритетов -1, а потом брали 0 и +1 соотв.
Re[3]: эффективная реализация thread pool
От: reversecode google
Дата: 17.05.18 10:58
Оценка:
B>О, спасибо за содержательный ответ, любопытно!

ничего особенного, код почти 1:1 с boost::asio который вы даже открывать не хотите
у вас короны случайно нет ? очень похожи на царя
Re[2]: эффективная реализация thread pool
От: reversecode google
Дата: 17.05.18 10:59
Оценка:
N>
N>  queue_mutex.lock();
N>  queue.push_back(new_work);
N>  queue_cv.signal();
N>  queue_mutex.unlock();
N>


только лучше сначала анлочить а потом будить, известная тема
Re[3]: эффективная реализация thread pool
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 11:03
Оценка:
Здравствуйте, reversecode, Вы писали:

N>>
N>>  queue_mutex.lock();
N>>  queue.push_back(new_work);
N>>  queue_cv.signal();
N>>  queue_mutex.unlock();
N>>


R>только лучше сначала анлочить а потом будить, известная тема


Да, согласен. В среднем лучше.
The God is real, unless declared integer.
Re[3]: эффективная реализация thread pool
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 11:09
Оценка:
Здравствуйте, barney, Вы писали:

B>Т.е все воркеры сразу блокируются на мьютексе очереди.

B>Затем один из них выгребает (непустую) очередь, убирает мьютекс и сразу же запускает work().
B>В это время какой то другой воркер ухватит мьютекс и пойдет запускать свою работу.

B>Только не понял, зачем мьютекс лочится вконце после work? work(); queue_mutex.lock();


Потому что
1) проверять очередь можно только под локом — иначе вы будете лезть без сериализации в общие потроха.
2) входить в queue_cv.wait() можно только имея захваченный лок (если это не сделать, скорее всего, оно вылетит).
Поэтому удобнее всего сделать lock() сразу после конца выполнения рабочей функции.

B>Только для симметрии чтобы компенсировать общий unlock? Я так понимаю можно просто сделать брейк без блокировки мьютекса, это будет чуть эффективнее, так?


Можно делать lock в начале итерации цикла и unlock в конце. Но тогда будет лишняя пара unlock/lock на каждую итерацию. Смысла нет.

B>Второй момент, возможно ли внутри воркера (вызванного из work()) вложенное обращение к диспетчеру же с какой то новой работой?


Возможно. Никаких проблем. Лок при этом не держится.

B>ну например вот так:


B>[code]

B>Dispatcher::dispatch([](){
B> do_worker1(); // эта лямбда выполняется внутри queue_mutex.unlock(); work(); queue_mutex.lock();
B> Dispatcher::dispatch([]() // здесь диспетчер попытается захватить мьютекс снова, но если он захвачен другим потоком — это безопасно ли? нет ли deadlock?

Нет. Главное, чтобы текущая задача не держала его.

B>Понятно, тут можно по сути вручную прикрутить любую кастомную логику.

B>Например помечать воркеры тегами — или ставить как то зависимости.
B>Можно даже приоритеты навернуть, чтобы при выборке работы — сначала шерстили всю очередь на предмет приоритетов -1, а потом брали 0 и +1 соотв.

Угу.
The God is real, unless declared integer.
Re: эффективная реализация thread pool
От: Кодт Россия  
Дата: 21.05.18 11:36
Оценка:
Здравствуйте, barney, Вы писали:

B>Привет желающим присоединиться к исследованию Thread Pool

B>Насколько я понимаю, Thread Pool это вариация на тему multiple producers/multiple consumers.

Не совсем. Потому что производители и потребители здесь начинают смешиваться.

B>Вопросы, которые не очень ясны.


B>1) Какой механизм меж-потоковой синхронизации?

B>Например я хочу гарантировать очередь исполнения моих ворков

B>допустим, из разных потоков я диспатчу:

B>dispatch(work1) | dispatch(work2)
B>я хочу чтобы work1 гарантированно исполнилась перед work2

Это значит, что ты по договорённости в этих потоках создаёшь составное задание.
Например, из первого потока отправляешь
// в первом потоке
dispatch( []() {
  work1();  // в случайном потоке
  dispatch( []() {
    work2();  // в другом случайном потоке
  } );
} );

Либо
// в первом потоке
auto future = dispatch(work1);
send_to_second_thread(future);
.....

// во втором потоке
auto future = receive_from_first_thread();
wait(future);
dispatch(work2);


Кстати, речь идёт о потоках из пула или о произвольных потоках?

B>при этом, чтобы потоки не простаивали

B>для этого work1 нужно не просто разместить в очереди до work2
B>а и гарантировать что поток, исполняющий work1 закончится до того, как другой поток возьмется за work2
B>Возможно тут нужны некие барьеры

Такие барьеры — это фьючерсы. В 11 стандарте уже есть в стандарте, а до того были в бусте.
Кури std::promise / std::future.

Ожидание фьючерсов, по-хорошему, должно возвращать поток обратно в пул.
Иначе может сложиться такая ситуация, что все работчие потоки что-то там модально запланировали и сидят-кукуют, а диспетчер видит, что, с одной стороны, у него очередь заявок, а с другой — что свободной кассы нет.
Вот именно поэтому я написал не future.wait(), а wait(future) — что диспетчер воспользуется моментом и превратит блокирующий вызов во что-то более человекополезное.
Перекуём баги на фичи!
Re[2]: эффективная реализация thread pool
От: barney  
Дата: 21.05.18 14:32
Оценка:
К>Либо
К>
К>// в первом потоке
К>auto future = dispatch(work1);
К>send_to_second_thread(future);
К>.....

К>// во втором потоке
К>auto future = receive_from_first_thread();
К>wait(future);
К>dispatch(work2);
К>


Эта схема мне не нравится — тк забираем ядро у пула — т.к просто в потоке ждем фьючера, а это значит что поток заблокирован
и изьят из пула.
Инетерсно можно ли это сделать как то элегантнее, т.е чтобы воркер просто не вызвался до получения этого фьючера.
Некий колбэк по факту завершения вычисления.

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

К>Кстати, речь идёт о потоках из пула или о произвольных потоках?


О пуле потоков. Тк издержки на поднятие thread большие, чем усыпить его по condition_variable
Поэтому я делаю N потоков, они в цикле выгребают воркеров из очереди и если работы нет — спят по cond_variable

К>Такие барьеры — это фьючерсы. В 11 стандарте уже есть в стандарте, а до того были в бусте.

К>Кури std::promise / std::future.

К>Ожидание фьючерсов, по-хорошему, должно возвращать поток обратно в пул.


Вот, то о чем я и говорю

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

К>Вот именно поэтому я написал не future.wait(), а wait(future) — что диспетчер воспользуется моментом и превратит блокирующий вызов во что-то более человекополезное.

Вот как это можно реализовать? Т.е future.get() это блокирующий вызов, а периодически опрашивать фьючер — тоже съедает ресурсы CPU
Т.к уже promise поток делает некие вычисления — то блокироваться на фьючере из другого потока — вообще смысла ни малейшего.
Нужен каокй то механизм уведомления.
Т.е promise.set должен не просто передавать данные в соотв. future — а каким то образом обращаться к диспетчеру, и просить запланировать след. воркер,
который сделает работу связанную с данным фьючером, future.get
не понятно как бы это на уровне апи выразить... т.к future.get() может быть внутри лямбды, и сам future ничего не знает о своей лямбде, да и вообще откуда он вызывается
Re: эффективная реализация thread pool
От: hi_octane Беларусь  
Дата: 21.05.18 15:30
Оценка:
B>Схематически, должно быть так:
Блокировок и ожиданий будет меньше если сделать не одну очередь на все потоки, а у каждого потока своя очередь. И только если локальная очередь пуста, тогда поток идёт и ищет работу у других.

Это называется work-stealing, и реализовано, полагаю, в каждой библиотеке в исходники которой советовали посмотреть.
Re[3]: эффективная реализация thread pool
От: AndrewJD США  
Дата: 21.05.18 15:47
Оценка:
Здравствуйте, barney, Вы писали:

B>Вот как это можно реализовать? Т.е future.get() это блокирующий вызов, а периодически опрашивать фьючер — тоже съедает ресурсы CPU

B>Т.к уже promise поток делает некие вычисления — то блокироваться на фьючере из другого потока — вообще смысла ни малейшего.

future::then позволяет указать колбэк, который вызвовится когда результат будет готов. 'future::then' есть в boost, facebook folly и будет в следующем стандарте.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re[3]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 21.05.18 16:37
Оценка:
Здравствуйте, barney, Вы писали:

К>>Либо

К>>
К>>// в первом потоке
К>>auto future = dispatch(work1);
К>>send_to_second_thread(future);
К>>.....

К>>// во втором потоке
К>>auto future = receive_from_first_thread();
К>>wait(future);
К>>dispatch(work2);
К>>


B>Эта схема мне не нравится — тк забираем ядро у пула — т.к просто в потоке ждем фьючера, а это значит что поток заблокирован

B>и изьят из пула.
B>Инетерсно можно ли это сделать как то элегантнее, т.е чтобы воркер просто не вызвался до получения этого фьючера.
B>Некий колбэк по факту завершения вычисления.

Я думаю, тут нужно сделать просто специальные фьючерсы — специфичные для тредпула.
Тогда ожидание как бы отдаёт поток в распоряжение тредпула, а исполнение обещания — планирует вернуть управление в поток, когда он размотает стек.

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

Гораздо бОльшая проблема состоит в том, что в плюсах нет сопрограмм со стеком. Поэтому, когда поток отдаётся в тредпул, он привозит к себе абсолютно неуправляемую латентность.
второй поток                                               исполнитель work1

second_task...
second_task...
wait(THE_FUTURE)
   another_task...
   another_task...
   wait(something_else)                                    work1...
      third_task...                                        THE_PROMISE.set_value
      third_task...
   awaited something_else - и вернёмся в another_task
   another_task...          а отнюдь не в second_task
   another_task...
awaited THE_FUTURE!
second_task...


B>Впрочем, самое простое — делать по первой схеме — вложенный диспатч, т.е просто вызывать сл. воркер по факту завершения работы первого.

B>Тогда и фьючерсы не нужны

Тогда нужен канал передачи информации из второго потока к исполнителю этого составного задания. (В общем случае, разумеется).
Либо мы наловчимся разбивать задачи так, чтобы они были бесстековыми.

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


Просто сделать my_threadpool_promise и my_threadpool_future.
Которые конструируются конкретным тредпулом, потому что они завязаны не на планировщика ОС, а на диспетчера конкретного тредпула.
Перекуём баги на фичи!
Re[4]: эффективная реализация thread pool
От: barney  
Дата: 21.05.18 17:18
Оценка:
К>
К>второй поток                                               исполнитель work1

К>second_task...
К>second_task...
К>wait(THE_FUTURE)
К>   another_task...
К>   another_task...
К>   wait(something_else)                                    work1...
К>      third_task...                                        THE_PROMISE.set_value
К>      third_task...
К>   awaited something_else - и вернёмся в another_task
К>   another_task...          а отнюдь не в second_task
К>   another_task...
К>awaited THE_FUTURE!
К>second_task...
К>


вот. в этом и проблема. нужно как то таски абстрагировать от потоков.
т.е если в левом потоке second_task повис на ожидании — воркер со стеком надо как то "закрыть"
и если там уже исполняется another_task то promise.set должен не прерывать стек первого потока,
а в новом потоке "раскрыть" second_task с его стеком.
не представляю, как это сделать вручную.
как вариант — в воркерах вообще запретить блокироваться по wait.
и любые зависимые переменные — вводить в новый воркер параметром

auto worker1 = []() -> int { return do_long_calc(); }
auto worker2 = [](int future_result){ use(future_result); }

Pool::dispatch(worker1).then(worker2); // здесь пул должен сам увидеть что worker1 возвращает значение,
которое нужно будет сохранить и запулить второму воркеру.
Re[5]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 21.05.18 17:59
Оценка:
Здравствуйте, barney, Вы писали:

К>>(иллюстрация бешеной латентности из-за рекурсии)


B>вот. в этом и проблема. нужно как то таски абстрагировать от потоков.

B>т.е если в левом потоке second_task повис на ожидании — воркер со стеком надо как то "закрыть"
B>и если там уже исполняется another_task то promise.set должен не прерывать стек первого потока,
B>а в новом потоке "раскрыть" second_task с его стеком.
B>не представляю, как это сделать вручную.
B>как вариант — в воркерах вообще запретить блокироваться по wait.
B>и любые зависимые переменные — вводить в новый воркер параметром

B>auto worker1 = []() -> int { return do_long_calc(); }

B>auto worker2 = [](int future_result){ use(future_result); }

B>Pool::dispatch(worker1).then(worker2); // здесь пул должен сам увидеть что worker1 возвращает значение,

B>которое нужно будет сохранить и запулить второму воркеру.

Ну да, сделать бесстековые сопрограммы, примерно так, как в яваскрипте. Или какое-нибудь пи-исчисление.
Каждая функция должна внутри не зависеть / не зависать от заданий, выполняющихся в этом тредпуле.
Перекуём баги на фичи!
Re[6]: эффективная реализация thread pool
От: reversecode google
Дата: 21.05.18 18:02
Оценка:
К>Ну да, сделать бесстековые сопрограммы, примерно так, как в яваскрипте. Или какое-нибудь пи-исчисление.
К>Каждая функция должна внутри не зависеть / не зависать от заданий, выполняющихся в этом тредпуле.

зачем читать топик
http://rsdn.org/forum/cpp/7147121.1
Автор: reversecode
Дата: 17.05.18
Re[4]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 21.05.18 18:12
Оценка:
Здравствуйте, AndrewJD, Вы писали:

AJD>future::then позволяет указать колбэк, который вызвовится когда результат будет готов. 'future::then' есть в boost, facebook folly и будет в следующем стандарте.


А сопрограммы будут в следующем стандарте?
Перетащат из буста?
Перекуём баги на фичи!
Re[5]: эффективная реализация thread pool
От: reversecode google
Дата: 21.05.18 18:22
Оценка:
К>А сопрограммы будут в следующем стандарте?
К>Перетащат из буста?

только стеклесс
Re[7]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 22.05.18 16:38
Оценка:
Здравствуйте, reversecode, Вы писали:

R>зачем читать топик

R>http://rsdn.org/forum/cpp/7147121.1
Автор: reversecode
Дата: 17.05.18


Не "читать", а слушать коротенький доклад на 43 минуты, из которого примерно полчаса мотивационной речи.

И я конечно понимаю, на гитхабе лежит самодокументирующийся код, бери-читай-используй...
Но...
Перекуём баги на фичи!
Re[8]: эффективная реализация thread pool
От: barney  
Дата: 22.05.18 16:48
Оценка:
К>Не "читать", а слушать коротенький доклад на 43 минуты, из которого примерно полчаса мотивационной речи.
К>И я конечно понимаю, на гитхабе лежит самодокументирующийся код, бери-читай-используй...
К>Но...

Вот вот,
да и код rethinkdb — дико запутанный ад, с кучей платформенных зависимостей, захардкоженных с помощью #ifdef в препроцессоре

Вот полюбуйтесь
  адский ад
asm(
#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined (__s390x__) || defined (__powerpc64__)
// We keep architecture-specific code interleaved in order to enforce commonality.
#if defined(__x86_64__)
#if defined(__LP64__) || defined(__LLP64__)
// Pointers are of the right size
#else
// Having non-native-sized pointers makes things very messy.
#error "Non-native pointer size."
#endif
#endif // defined(__x86_64__)
".text\n"
"_lightweight_swapcontext:\n"

#if defined(__i386__)
    /* `current_pointer_out` is in `4(%ebp)`. `dest_pointer` is in `8(%ebp)`. */
#elif defined(__x86_64__)
    /* `current_pointer_out` is in `%rdi`. `dest_pointer` is in `%rsi`. */
#elif defined(__arm__)
    /* `current_pointer_out` is in `r0`. `dest_pointer` is in `r1` */
#elif defined(__s390x_)
    /* `current_pointer_out` is in `%r2`. `dest_pointer` is in `%r3`. */
#elif defined(__powerpc64__)
    /* `current_pointer_out` is in `r3`. `dest_pointer` is in `r4` */
#endif

    // Save preserved registers.
#if defined(__i386__)
    // Preserve esi, edi, ebx, and ebp. The return address is already on the stack.
    "push %esi\n"
    "push %edi\n"
    "push %ebx\n"
    "push %ebp\n"
#elif defined(__x86_64__)
    // Preserve r12-r15, rbx, and rbp. The return address is already on the stack.
    "pushq %r12\n"
    "pushq %r13\n"
    "pushq %r14\n"
    "pushq %r15\n"
    "pushq %rbx\n"
    "pushq %rbp\n"
#elif defined(__arm__)
    // Preserve r4-r12 and the return address (r14). For consistency with x86 r12 is
    // pushed first, followed by r14 and then r4-r11.
    "push {r12}\n"
    "push {r14}\n"
    "push {r4-r11}\n"
#elif defined(__arm64__) || defined(__aarch64__)
    // Preserve d8-d15 + x19-x29 and the return address (x30).
    // Note: x30 is stored twice due to alignment requirements
    "sub sp, sp, #0xb0\n"
    "stp d8,  d9,  [sp, #0x00]\n"
    "stp d10, d11, [sp, #0x10]\n"
    "stp d12, d13, [sp, #0x20]\n"
    "stp d14, d15, [sp, #0x30]\n"
    "stp x19, x20, [sp, #0x40]\n"
    "stp x21, x22, [sp, #0x50]\n"
    "stp x23, x24, [sp, #0x60]\n"
    "stp x25, x26, [sp, #0x70]\n"
    "stp x27, x28, [sp, #0x80]\n"
    "stp x29, x30, [sp, #0x90]\n"
    "str x30, [sp, #0xa0]\n"
#elif defined(__s390x__)
    // Preserve r6-r13, the return address (r14), and f8-f15.
    "aghi %r15, -136\n"
    "stmg %r6, %r14, 64(%r15)\n"
    "std %f8, 0(%r15)\n"
    "std %f9, 8(%r15)\n"
    "std %f10, 16(%r15)\n"
    "std %f11, 24(%r15)\n"
    "std %f12, 32(%r15)\n"
    "std %f13, 40(%r15)\n"
    "std %f14, 48(%r15)\n"
    "std %f15, 56(%r15)\n"
#elif defined(__powerpc64__)
    "addi 1, 1, -(21*8)\n"
    "std 2, (8*0)(1)\n"
    "std 14, (8*1)(1)\n"
    "std 15, (8*2)(1)\n"
    "std 16, (8*3)(1)\n"
    "std 17, (8*4)(1)\n"
    "std 18, (8*5)(1)\n"
    "std 19, (8*6)(1)\n"
    "std 20, (8*7)(1)\n"
    "std 21, (8*8)(1)\n"
    "std 22, (8*9)(1)\n"
    "std 23, (8*10)(1)\n"
    "std 24, (8*11)(1)\n"
    "std 25, (8*12)(1)\n"
    "std 26, (8*13)(1)\n"
    "std 27, (8*14)(1)\n"
    "std 28, (8*15)(1)\n"
    "std 29, (8*16)(1)\n"
    "std 30, (8*17)(1)\n"
    "std 31, (8*18)(1)\n"
    "mfcr 0\n"
    "std 0, (8*19)(1)\n"
    "mflr 0\n"
    "std 0, (8*20)(1)\n"
#endif

    /* Save old stack pointer. */
#if defined(__i386__)
    /* i386 passes arguments on the stack. We add ((number of things pushed)+1)*(sizeof(void*)) to esp in order to get the first argument. */
    "mov 20(%esp), %ecx\n"
    /* We then copy the stack pointer into the space indicated by the first argument. */
    "mov %esp, (%ecx)\n"
#elif defined(__x86_64__)
    /* On amd64, the first argument comes from rdi. */
    "movq %rsp, (%rdi)\n"
#elif defined(__arm__)
    /* On ARM, the first argument is in `r0`. `r13` is the stack pointer. */
    "str r13, [r0]\n"
#elif defined(__arm64__) || defined(__aarch64__)
    /* On ARM64, the first argument is in `x0`. `sp` is the stack pointer and `x4` is a scratch register. */
    "mov x4, sp\n"
    "str x4, [x0]\n"
#elif defined(__s390x__)
    /* On s390x, the first argument is in r2. r15 is the stack pointer. */
    "stg %r15, 0(%r2)\n"
#elif defined(__powerpc64__)
    "std  1, 0(3)\n"
#endif

    /* Load the new stack pointer and the preserved registers. */
#if defined(__i386__)
    /* i386 passes arguments on the stack. We add ((number of things pushed)+1)*(sizeof(void*)) to esp in order to get the first argument. */
    "mov 24(%esp), %esi\n"
    /* We then copy the second argument to be the new stack pointer. */
    "mov %esi, %esp\n"
#elif defined(__x86_64__)
    /* On amd64, the second argument comes from rsi. */
    "movq %rsi, %rsp\n"
#elif defined(__arm__)
    /* On ARM, the second argument is in `r1` */
    "mov r13, r1\n"
#elif defined(__arm64__) || defined(__aarch64__)
    /* On ARM64, the second argument is in `x1` */
    "mov sp, x1\n"
#elif defined(__s390x__)
    /* On s390x, the second argument is in r3 */
    "lgr %r15, %r3\n"
#elif defined(__powerpc64__)
    "mr 1, 4\n"
#endif

#if defined(__i386__)
    "pop %ebp\n"
    "pop %ebx\n"
    "pop %edi\n"
    "pop %esi\n"
#elif defined(__x86_64__)
    "popq %rbp\n"
    "popq %rbx\n"
    "popq %r15\n"
    "popq %r14\n"
    "popq %r13\n"
    "popq %r12\n"
#elif defined(__arm__)
    "pop {r4-r11}\n"
    "pop {r14}\n"
    "pop {r12}\n"
#elif defined(__arm64__) || defined(__aarch64__)
    "ldp d8,  d9,  [sp, #0x00]\n"
    "ldp d10, d11, [sp, #0x10]\n"
    "ldp d12, d13, [sp, #0x20]\n"
    "ldp d14, d15, [sp, #0x30]\n"
    "ldp x19, x20, [sp, #0x40]\n"
    "ldp x21, x22, [sp, #0x50]\n"
    "ldp x23, x24, [sp, #0x60]\n"
    "ldp x25, x26, [sp, #0x70]\n"
    "ldp x27, x28, [sp, #0x80]\n"
    "ldp x29, x30, [sp, #0x90]\n"
    "ldr x4, [sp, #0xa0]\n"
    "add sp, sp, #0xb0\n"
#elif defined(__s390x__)
    "lmg %r6, %r14, 64(%r15)\n"
    "ld %f8, 0(%r15)\n"
    "ld %f9, 8(%r15)\n"
    "ld %f10, 16(%r15)\n"
    "ld %f11, 24(%r15)\n"
    "ld %f12, 32(%r15)\n"
    "ld %f13, 40(%r15)\n"
    "ld %f14, 48(%r15)\n"
    "ld %f15, 56(%r15)\n"
    "aghi %r15, 136\n"
#elif defined(__powerpc64__)
    "ld 2, (8*0)(1)\n"
    "ld 14, (8*1)(1)\n"
    "ld 15, (8*2)(1)\n"
    "ld 16, (8*3)(1)\n"
    "ld 17, (8*4)(1)\n"
    "ld 18, (8*5)(1)\n"
    "ld 19, (8*6)(1)\n"
    "ld 20, (8*7)(1)\n"
    "ld 21, (8*8)(1)\n"
    "ld 22, (8*9)(1)\n"
    "ld 23, (8*10)(1)\n"
    "ld 24, (8*11)(1)\n"
    "ld 25, (8*12)(1)\n"
    "ld 26, (8*13)(1)\n"
    "ld 27, (8*14)(1)\n"
    "ld 28, (8*15)(1)\n"
    "ld 29, (8*16)(1)\n"
    "ld 30, (8*17)(1)\n"
    "ld 31, (8*18)(1)\n"
    "ld 0, (8*19)(1)\n"
    "mtcr 0\n"
    "ld 0, (8*20)(1)\n"
    "mtlr 0\n"
    "addi 1, 1, (8*21)\n"
#endif

#if defined(__i386__) || defined(__x86_64__)
    /* The following ret should return to the address set with
    `artificial_stack_t()` or with the previous `lightweight_swapcontext`. The
    instruction pointer is saved on the stack from the previous call (or
    initialized with `artificial_stack_t()`). */
    "ret\n"
#elif defined(__arm__)
    /* Above, we popped `LR` (`r14`) off the stack, so the bx instruction will
    jump to the correct return address. */
    "bx r14\n"
#elif defined(__arm64__) || defined(__aarch64__)
    /* Above, we stored the `x30` the return address in a variable register `x4` so the ret instruction will
    return it to jump. */
    "ret x4\n"
#elif defined(__s390x__)
    /* Above, we popped the return address (r14) off the stack. */
    "br %r14\n"
#elif defined(__powerpc64__)
    "blr\n"
#endif

#else
#error "Unsupported architecture."
#endif
);


Просто, я вот, вовсе не уверен что это заведется на всех платформах — ios, android, mac, linux, win
Да, пацаны круты — взяли и запилили корутины с переключением контекста,
но было бы интересно c++11 / posix решение без платформенных ассемблеров и жести
Re[9]: эффективная реализация thread pool
От: reversecode google
Дата: 22.05.18 17:53
Оценка:
да вперед, я посмотрю как вы сделаете переключение стека без ассемблера
стекфул корутины в бусте не зря на асме сделали, а не на longjump
но вы предпочитаете разбить лоб, сломать ногу, потерять руку и время, но самостоятельно доказать что это не возможно
Re[8]: эффективная реализация thread pool
От: reversecode google
Дата: 22.05.18 17:57
Оценка:
К>Не "читать", а слушать коротенький доклад на 43 минуты, из которого примерно полчаса мотивационной речи.

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

К>И я конечно понимаю, на гитхабе лежит самодокументирующийся код, бери-читай-используй...

К>Но...

услышать это от какого то новичка я бы еще понял, но синьеру с не первым годом в С++ разобраться в трех строчках кода ...
Re[9]: эффективная реализация thread pool
От: Кодт Россия  
Дата: 22.05.18 22:21
Оценка:
Здравствуйте, reversecode, Вы писали:

R>услышать это от какого то новичка я бы еще понял, но синьеру с не первым годом в С++ разобраться в трех строчках кода ...


В которых трёх строчках кода из тех нескольки тысяч?
Просто вот рыться в библиотеке и реконструировать её дизайн и "что хотел сделать автор" (и явно автор не хотел сделать документацию) — могу, но без удовольствия.
Перекуём баги на фичи!
Re[10]: эффективная реализация thread pool
От: barney  
Дата: 23.05.18 02:02
Оценка:
R>да вперед, я посмотрю как вы сделаете переключение стека без ассемблера
R>стекфул корутины в бусте не зря на асме сделали, а не на longjump

а, удачи, берите себе в проект эту хрень из тысячи файлов, с platform specific кодом для 5 архитектур.
выйдет новый Интел, на котором все поломается, или очередной какой либо ARM128 — вам потом и поддерживать это всё,
если автор забьет.
завязывать себя на такое "решение" — зачем, чтобы усложнить себе жизнь?
для начала, поднять + оттестировать на имеющийся уже андроид с его 10000+ устройствами и сотнями разных SoC — удачи )

R>но вы предпочитаете разбить лоб, сломать ногу, потерять руку и время,


фууу, ну и помойный бред...
что то у тебя с головой явно не то, раз такие фантазии
Re[10]: эффективная реализация thread pool
От: reversecode google
Дата: 23.05.18 04:15
Оценка:
в тех которые в папке конкуренси
Re[11]: эффективная реализация thread pool
От: reversecode google
Дата: 23.05.18 04:20
Оценка:
вы упоротый ? где я вам сказал "бери" ?
я вам показал решение где это реализовано и можно изучить как оно устроено
но я вижу вы большой теоретик исследователь в теме "выпить стакан воды с закрытым ртом"
варианты вы уже почти все перебрали,
— залить через нос
— клизьмой через...
— подсказываю, еще внутривенно

ждем от вас результатов
Re[12]: эффективная реализация thread pool
От: barney  
Дата: 23.05.18 06:39
Оценка:
R>вы упоротый ? где я вам сказал "бери" ?
R>я вам показал решение где это реализовано и можно изучить как оно устроено
R>но я вижу вы большой теоретик исследователь в теме "выпить стакан воды с закрытым ртом"
R>варианты вы уже почти все перебрали,
R>- залить через нос
R>- клизьмой через...
R>- подсказываю, еще внутривенно

божежмой,
за ссылку спасибо, конечно,
но зачем же так негативить? что это за потоки каловых масс
вам же уже объяснили — ветка создана для разбора решений,
и общения по интересам
Re[11]: эффективная реализация thread pool
От: alex_public  
Дата: 24.05.18 02:02
Оценка:
Здравствуйте, barney, Вы писали:

R>>да вперед, я посмотрю как вы сделаете переключение стека без ассемблера

R>>стекфул корутины в бусте не зря на асме сделали, а не на longjump
B>а, удачи, берите себе в проект эту хрень из тысячи файлов, с platform specific кодом для 5 архитектур.
B>выйдет новый Интел, на котором все поломается, или очередной какой либо ARM128 — вам потом и поддерживать это всё,
B>если автор забьет.
B>завязывать себя на такое "решение" — зачем, чтобы усложнить себе жизнь?
B>для начала, поднять + оттестировать на имеющийся уже андроид с его 10000+ устройствами и сотнями разных SoC — удачи )

Это ты уже какую-то чушь пишешь. В библиотеку Boost.Context (которая как раз отвечает за переключение стеков в сопрограммах) входит всего по 3 очень простеньких asm файла на каждую архитектуру процессора (а вот их побольше чем 5, хотя и не сильно). Так что при появление гипотетического arm128 написание версии под него скорее всего займёт меньше одного часа...
Re[12]: эффективная реализация thread pool
От: barney  
Дата: 24.05.18 19:58
Оценка:
_>Это ты уже какую-то чушь пишешь. В библиотеку Boost.Context (которая как раз отвечает за переключение стеков в сопрограммах) входит всего по 3 очень простеньких asm файла на каждую архитектуру процессора (а вот их побольше чем 5, хотя и не сильно). Так что при появление гипотетического arm128 написание версии под него скорее всего займёт меньше одного часа...

Речь была о rethinkdb.

Boost в этом плане интереснее, да и тяготеет становиться стандартом,
но, вроде бы пока что он плохо заводится на Android NDK
Re[13]: эффективная реализация thread pool
От: reversecode google
Дата: 24.05.18 20:22
Оценка:
B>Boost в этом плане интереснее, да и тяготеет становиться стандартом,
B>но, вроде бы пока что он плохо заводится на Android NDK

вы свои фантазии оставьте при себе
яндекс уже на видео рассказал как у них карты на андроиде и айосе на этих корутинах работают
и не только эти сервисы, но это уже умение ресерчить что бы узнать
Re[14]: эффективная реализация thread pool
От: barney  
Дата: 25.05.18 00:44
Оценка: +1
R>вы свои фантазии оставьте при себе
R>яндекс уже на видео рассказал как у них карты на андроиде и айосе на этих корутинах работают
R>и не только эти сервисы, но это уже умение ресерчить что бы узнать

че ты такой важный? на умняке, как академик, вещаешь с таким гонором — "фантазии" "умение ресерчить" — типа, куда ж нам, простым смертным )
в детстве обижали?) как же нелепо это выглядит)))
будь проще, если есть что сказать,
а то пафосу столько, что гляди лопнешь от собственной важности и превосходства )
Re[13]: эффективная реализация thread pool
От: alex_public  
Дата: 26.05.18 00:15
Оценка:
Здравствуйте, barney, Вы писали:

_>>Это ты уже какую-то чушь пишешь. В библиотеку Boost.Context (которая как раз отвечает за переключение стеков в сопрограммах) входит всего по 3 очень простеньких asm файла на каждую архитектуру процессора (а вот их побольше чем 5, хотя и не сильно). Так что при появление гипотетического arm128 написание версии под него скорее всего займёт меньше одного часа...

B>Речь была о rethinkdb.

Причём тут какой-то rethinkdb, если ты писал свой ответ на вполне однозначную фразу "стекфул корутины в бусте не зря на асме сделали, а не на longjump"?

B>Boost в этом плане интереснее, да и тяготеет становиться стандартом,

B>но, вроде бы пока что он плохо заводится на Android NDK

Естественно, что не все библиотеки Boost'a собираются под Андроид (причём это не какие-то баги, а вполне себе заранее заложенная и документированная ситуация). Однако с обсуждаемыми библиотеками (переключения контекстов, сопрограммы и т.п.) никаких проблем нет. И это вполне предсказуемо — откуда возьмутся проблемы у банального ассемблерного кода сохранения/восстановления регистров? Там сложности в основном у библиотек требующих всяческий специфичный системный API...
Re[14]: эффективная реализация thread pool
От: barney  
Дата: 26.05.18 08:26
Оценка:
_>Причём тут какой-то rethinkdb, если ты писал свой ответ на вполне однозначную фразу "стекфул корутины в бусте не зря на асме сделали, а не на longjump"?

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

_>Естественно, что не все библиотеки Boost'a собираются под Андроид (причём это не какие-то баги, а вполне себе заранее заложенная и документированная ситуация). _>

Однако с обсуждаемыми библиотеками (переключения контекстов, сопрограммы и т.п.) никаких проблем нет.

ясно, хорошо

_> И это вполне предсказуемо — откуда возьмутся проблемы у банального ассемблерного кода сохранения/восстановления регистров?


ясно, сохранение контекста/регистров на платформенных асмах — не проблема для библиотеки

_> Там сложности в основном у библиотек требующих всяческий специфичный системный API...


м... я думал, boost опирается только на c++ std / posix и с андроидом проблема именно в совместимости с NDK
(там, например нет стандартной C библиотеки, там bionic)

про Context / Coroutines
вообще это интересно, вполне можно использовать

Технический практический вопрос:
Как именно сделать некий легковесный пул задач на корутинах?

Я так понимаю, корутины дают легковесное переключение "контекста" т.к оным там является переключение (сохранение/восстановление)
указателя инструкций + текущего стека + регистров.
И это эффективнее вызова планировщика ОС

Плюс, позволяет писать более "синхронный" код, как в лекции про яндекс.карты.

Интересно, как именно сделать "асинхронный фьючерс" и wait() — например, ожидание ввода-вывода, сети, результата работы другой корутины и т.д.
Re[4]: эффективная реализация thread pool
От: Слава  
Дата: 26.05.18 08:33
Оценка:
Здравствуйте, reversecode, Вы писали:

R>ничего особенного, код почти 1:1 с boost::asio который вы даже открывать не хотите

R>у вас короны случайно нет ? очень похожи на царя

Уймитесь, а. Вам русский язык не нравится, писать-читать на нём? Исходники предпочитаете?
Re[15]: эффективная реализация thread pool
От: vopl Россия  
Дата: 26.05.18 09:57
Оценка:
Здравствуйте, barney, Вы писали:

...
B>про Context / Coroutines
B>вообще это интересно, вполне можно использовать

B>Технический практический вопрос:

B>Как именно сделать некий легковесный пул задач на корутинах?

B>Я так понимаю, корутины дают легковесное переключение "контекста" т.к оным там является переключение (сохранение/восстановление)

B>указателя инструкций + текущего стека + регистров.
B>И это эффективнее вызова планировщика ОС

B>Плюс, позволяет писать более "синхронный" код, как в лекции про яндекс.карты.


B>Интересно, как именно сделать "асинхронный фьючерс" и wait() — например, ожидание ввода-вывода, сети, результата работы другой корутины и т.д.


Эту ссылку уже приводили выше, там именно про асинхронный фьючерс, wait rethinkdb-concurrency

Если пропрет поразбираться просто из спортивного интереса — могу показать еще такое:
управлятор пулом задач на корутинах

от него вниз:
собственно, корутина
движок контекстов от буста рядом есть еще posix-ucontext

и вверх:
ожидатор
пример объекта синхронизации 'событие' там рядом еще много других всяких

future
promise
их ядро

кишки устройства задачи
запуск задачи

некоторые примеры использования:
запуск задачи
синхронизация
future/promise

B>... ожидание ввода-вывода

файловый дескриптор (он же сокет)
ожидание событий на дескрипторах, epoll
Re[15]: эффективная реализация thread pool
От: vopl Россия  
Дата: 29.05.18 08:00
Оценка:
Здравствуйте, barney, Вы писали:

...

B>про Context / Coroutines

B>вообще это интересно, вполне можно использовать

B>Технический практический вопрос:

B>Как именно сделать некий легковесный пул задач на корутинах?

B>Я так понимаю, корутины дают легковесное переключение "контекста" т.к оным там является переключение (сохранение/восстановление)

B>указателя инструкций + текущего стека + регистров.
B>И это эффективнее вызова планировщика ОС

B>Плюс, позволяет писать более "синхронный" код, как в лекции про яндекс.карты.


B>Интересно, как именно сделать "асинхронный фьючерс" и wait() — например, ожидание ввода-вывода, сети, результата работы другой корутины и т.д.


еще можно посмотреть mordor
Re[15]: эффективная реализация thread pool
От: alex_public  
Дата: 29.05.18 17:20
Оценка:
Здравствуйте, barney, Вы писали:

_>> Там сложности в основном у библиотек требующих всяческий специфичный системный API...

B>м... я думал, boost опирается только на c++ std / posix и с андроидом проблема именно в совместимости с NDK
B>(там, например нет стандартной C библиотеки, там bionic)

Ну во-первых там не только bionic, но есть и libc и libstdc++. Но дело как раз не в них. Наоборот, главной плюс Boost'а в том, что он абстрагирует многие платфомозависимые вещи, имея при этом внутри себя по специфической (оптимальной) реализации под каждую из платформ. Например какой-нибудь там asio под виндой работает через родной асинхронный интерфейс winapi. Ну так вот некоторые из таких платформозависимых библиотек Boost'а не имеют версий под Андроид.

B>про Context / Coroutines

B>вообще это интересно, вполне можно использовать
B>Технический практический вопрос:
B>Как именно сделать некий легковесный пул задач на корутинах?

Для реализации лёгких (их ещё часто называют зелёными) потоков сопрограммы не нужны. Для этого достаточно как раз переключателя контекстов. Сопрограммы — это нечто большее, что позволяет делать потоки с "несколькими входами".

B>Я так понимаю, корутины дают легковесное переключение "контекста" т.к оным там является переключение (сохранение/восстановление)

B>указателя инструкций + текущего стека + регистров.
B>И это эффективнее вызова планировщика ОС

Да, нормальное переключение системных потоков идёт через ядро ОС, что весьма тяжело. А такие лёгкие потоки потоки переключаются почти мгновенно, но у этого есть своя цена — у нас тут снова (как в первых версиях винды) получается кооперативная, а не вытесняющая многозадачность.

B>Плюс, позволяет писать более "синхронный" код, как в лекции про яндекс.карты.


Да, вот для написание асинхронного кода, выглядящего как синхронный, сопрограммы и нужны.

B>Интересно, как именно сделать "асинхронный фьючерс" и wait() — например, ожидание ввода-вывода, сети, результата работы другой корутины и т.д.


Ну вот например здесь http://rsdn.org/forum/philosophy/5353890.1
Автор: alex_public
Дата: 06.11.13
можно увидеть вариацию на тему. Правда там используется совсем старая версия бустовских сопрограмм (они с тех пор существенно изменились) и ключевая цель там чуть другая (обработка в UI потоке результатов неких асинхронных операций, каждая из которых представляют собой отдельные системные потоки, исполняющие синхронные операции).
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.