All streams
Search
Write a publication
Pull to refresh
7
0
Send message
Ну, тогда нужно сделать класс cancelable_function<...>, который будет знать, что выполнять, и как это остановить

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


Тут тоже надо знать, что безопасно для той или иной функции в том или ином контексте. В частности, если функция, которая запускается в потоке, не известна программисту, нужно будет от автора получить или cancellation_handler, который потом использовать при создании токена <...>

Я не вполне понял, что вы имеете в виду под "известна программисту", но от автора ничего получать не надо. Токены не требуют для своего создания cancellation_handler, токен создаётся и живёт вместе с потоком. Когда в этом потоке вызывается прерываемая функция — она использует токен для того, чтобы проверять статус прерывания. Если эта функция вызывает другую прерываемую функцию — она передаёт ей ссылку на этот токен.
Вот небольшой пример программы, которая обрабатывает задачи от пользователя в отдельном потоке:


void do_subtask(const subtask_data& data, const cancellation_token& token)
{
  // ...
}

void do_task(const task_data& data, const cancellation_token& token)
{
  for (int i = 0; i < Subtasks && token; ++i)
    do_subtask(data.get_subtask(i), token);
}

void do_work(concurrent_queue<task_data>& tasks, const cancellation_token& token)
{
  while(token)
  {
    auto data = tasks.pop(token);
    if (data)
      do_task(*data, token);
  }
}

int main()
{
  concurrent_queue<task_data> tasks;
  rethread::thread t{ [&tasks] (const cancellation_token& token) { do_work(tasks, token); } };
  for (auto i = read_input(); i; i = read_input())
    tasks.push(*i);
  return 0;
}

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

Спасибо.


С февраля библиотека обросла тестами, документацией и переехала на атомики вместо мьютексов, что отлично сказалось на производительности.

Проблема в том, что сам по себе join() ситуацию не улучшит (это обсуждается в разделе "Серьёзная проблема").


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


Explicit оператору приведения к булеану нужен для того, чтобы не компилировался следующий код:


standalone_cancellation_token token;
int i = token + 5;

Добрый день.


pthread_cleanup_push/pthread_cleanup_pop я упоминать не стал, потому что плюсах им есть отличная альтернатива: RAII.


Проблема с обработчиками прерывания в Си в том, что их не все пишут. Из-за этого минусы использования pthread_cancel могут проявиться не сразу, а с задержкой.


Реальная история: в проекте появляется ещё одна 3-party библиотека, написанная на Си (обёртка над специфичным железом). Без исходного кода и связи с разработчиками библиотеки. Через несколько месяцев обнаруживаются странные дэдлоки, которых быть не должно. После нескольких дней дебага выясняется, что если вызвать pthread_cancel для потока в тот момент, когда этот поток находится внутри этой библиотеки — то он просто тихо умирает, даже не разматывая свой стек.


Насчёт отдельных задач — я имел в виду отмену долгой задачи без прерывания всего потока. Например, в приложении-навигаторе может быть отдельный поток для того, чтобы рассчитывать маршрут. Рассчёт маршрута из точки А в точку Б — это отдельная задача. Если пользователь изменил начальную и конечную точки — расчёт можно отменить, но прерывать весь поток смысла нет.

Для того, чтобы прерывать блокирующие IO-операции, необходимо реализовать соответствующий cancellation_handler. Для POSIX'а можно использовать такую схему — вместо того, чтобы ждать данных в read, места во write, подключения в accept и т.д. — можно всё ожидание делать в вызове poll. Тогда для отмены блокирующиего IO достаточно использовать прерываемую версию poll, которая уже есть в rethread: https://github.com/bo-on-software/rethread/blob/master/rethread/poll.hpp

Information

Rating
Does not participate
Registered
Activity