Подтвердить что ты не робот

Должен ли std:: future:: wait использовать столько CPU? Есть ли более эффективный вызов?

EDIT: tl; dr - эта проблема, по-видимому, ограничена небольшим набором комбинаций OS/компилятор/библиотека и теперь отслеживается в GCC Bugzilla как Ошибка 68921 благодаря @JonathanWakely.

Я жду в будущем, и я заметил, что top показывает 100% использование ЦП и strace показывает постоянный поток вызовов futex:

...
[pid 15141] futex(0x9d19a24, FUTEX_WAIT, -2147483648, {4222429828, 3077922816}) = -1 EINVAL (Invalid argument)
...

Это на Linux 4.2.0 (32-разрядный i686), скомпилированный с версией gcc версии 5.2.1.

Вот моя минимально жизнеспособная примерная программа:

#include <future>
#include <iostream>
#include <thread>
#include <unistd.h>

int main() {
  std::promise<void> p;
  auto f = p.get_future();

  std::thread t([&p](){
    std::cout << "Biding my time in a thread.\n";
    sleep(10);
    p.set_value();
  });

  std::cout << "Waiting.\n";
  f.wait();
  std::cout << "Done.\n";

  t.join();
  return 0;
}

и вот вызов компилятора (такое же поведение без -g):

g++ --std=c++11 -Wall -g -o spin-wait spin-wait.cc -pthread

Есть ли более эффективная альтернатива?

Вот логически-подобная программа, использующая std::condition_variable, которая выглядит намного лучше:

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <unistd.h>

int main() {
  bool done = 0;
  std::mutex m;
  std::condition_variable cv;

  std::thread t([&m, &cv, &done](){
    std::cout << "Biding my time in a thread.\n";
    sleep(10);
    {
      std::lock_guard<std::mutex> lock(m);
      done = 1;
    }
    cv.notify_all();
  });

  std::cout << "Waiting.\n";
  {
    std::unique_lock<std::mutex> lock(m);
    cv.wait(lock, [&done]{ return done; });
  }
  std::cout << "Done.\n";

  t.join();
  return 0;
}

Я делаю что-то не так с моим кодом std::future, или это реализация в моем libstdc++ просто так плохо?

4b9b3361

Ответ 1

Нет, конечно, это не должно быть сделано, это ошибка в реализации, а не свойство std::future.

Теперь это https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68921 - цикл, который вызывает вызов futex(2), находится в __atomic_futex_unsigned::_M_load_and_test_until

Он выглядит как простой отсутствующий аргумент функции syscall, поэтому указатель мусора передается ядру, который жалуется, что он не является допустимым аргументом timespec*. Я тестирую исправление и буду совершать завтра, поэтому он будет исправлен в GCC 5.4

Ответ 2

Нет, не следует. Обычно он отлично работает.

(В комментариях мы пытаемся определить больше о конкретной сломанной конфигурации, в которой результирующий исполняемый файл, похоже, затягивается, но я считаю, что ответ. Было бы неплохо определить, является ли это еще spin-wait на 32-битной цели в последнем g++.)

promise - это "толчок" будущего канала связи будущего будущего: операция, которая хранит значение в общем состоянии синхронизируется с (как определено в std::memory_order) успешным возвратом из любой функции, ожидающей в общем состоянии (например, std::future::get).

Я предполагаю, что это включает std::future::wait.

[ std::promise::set_value] Атомно сохраняет значение в общем состоянии и готовит состояние. Операция ведет себя так, как будто set_value, set_exception, set_value_at_thread_exit и set_exception_at_thread_exit приобретают один мьютекс, связанный с объектом обещания, при обновлении объекта обещания.

Хотя это немного тревожит то, что они описывают синхронизацию с точки зрения объекта -обещающего, а не общего состояния, намерение довольно ясно.

cppreference.com [*] продолжает использовать его точно так, как он не работал в вопросе выше. ( "Этот пример показывает, как обетование можно использовать в качестве сигналов между потоками".)