Русский
Русский
English
Статистика
Реклама

Прерывания

Перевод Новый поток в C20 stdjthread

22.03.2021 18:15:22 | Автор: admin

Привет, Хабр! Перевод статьи подготовлен в рамках курса "C++ Developer. Professional"


Один из участников моего семинара в рамках CppCon 2018 спросил меня: Может ли std::thread быть прерван (interrupted)?. Мой ответ тогда был нет, но это уже не совсем так. С C++20 мы можем получить std::jthread (в итоге все таки получили прим. переводчика).

Позвольте мне развить тему, поднятую на CppCon 2018. Во время перерыва в моем семинаре, посвященному параллелизму, я побеседовал с Николаем (Йосуттисом). Он спросил меня, что я думаю о новом предложении P0660: Cooperatively Interruptible Joining Thread. На тот момент я ничего не знал об этом предложении. Следует отметить, что Николай является одним из авторов этого предложения (наряду с Хербом Саттером и Энтони Уильямсом). Сегодняшняя статья посвящена будущему параллелизма в C++. Ниже я привел общую картину параллелизма в текущем и грядущем C++.

Из названия документа Cooperatively Interruptible Joining Thread (совместно прерываемый присоединяемый поток) вы можете догадаться, что новый поток имеет две новые возможности: прерываемость (interruptible) и автоматическое присоединение (automatically joining, здесь и далее присоединение блокировка вызывающего потока до завершения выполнения, результат вызова метода join() прим. переводчика). Позвольте мне сначала рассказать вам об автоматическом присоединении.

Автоматическое присоединение

Это неинтуитивное поведение std::thread. Если std::thread все еще является joinable, то в его деструкторе вызывается std::terminate. Поток thr является joinable, если ни thr.join(), ни thr.detach() еще не были вызваны.

// threadJoinable.cpp#include <iostream>#include <thread>int main(){        std::cout << std::endl;    std::cout << std::boolalpha;        std::thread thr{[]{ std::cout << "Joinable std::thread" << std::endl; }};        std::cout << "thr.joinable(): " << thr.joinable() << std::endl;        std::cout << std::endl;    }

При выполнении программа терминируется.

Оба потока терминируются. На втором запуске поток th имеет достаточно времени, чтобы отобразить свое сообщение: Joinable std::thread.

В следующем примере я заменяю хедер <thread> на "jthread.hpp" и использую std::jthread из грядущего стандарта C++.

// jthreadJoinable.cpp#include <iostream>#include "jthread.hpp"int main(){        std::cout << std::endl;    std::cout << std::boolalpha;        std::jthread thr{[]{ std::cout << "Joinable std::thread" << std::endl; }};        std::cout << "thr.joinable(): " << thr.joinable() << std::endl;        std::cout << std::endl;    }

Теперь поток thr автоматически присоединяется в своем деструкторе, если он все еще является joinable, как, например, в этом примере.

Прерывание std::jthread

Чтобы было от чего отталкиваться, позвольте мне продемонстрировать вам простой пример.

// interruptJthread.cpp#include "jthread.hpp"#include <chrono>#include <iostream>using namespace::std::literals;int main(){        std::cout << std::endl;        std::jthread nonInterruptable([]{                                   // (1)        int counter{0};        while (counter < 10){            std::this_thread::sleep_for(0.2s);            std::cerr << "nonInterruptable: " << counter << std::endl;             ++counter;        }    });        std::jthread interruptable([](std::interrupt_token itoken){         // (2)        int counter{0};        while (counter < 10){            std::this_thread::sleep_for(0.2s);            if (itoken.is_interrupted()) return;                        // (3)            std::cerr << "interruptable: " << counter << std::endl;             ++counter;        }    });        std::this_thread::sleep_for(1s);        std::cerr << std::endl;    std::cerr << "Main thread interrupts both jthreads" << std:: endl;    nonInterruptable.interrupt();    interruptable.interrupt();                                          // (4)        std::cout << std::endl;    }

Я запустил в main два потока, nonInterruptable, который нельзя прерывать, и interruptable, который можно (строки 1 и 2). В отличие от потока nonInterruptable, поток interruptable, получает std::interrupt_token и использует его в строке 3, чтобы проверить, был ли он прерван: itoken.is_interrupted(). В случае прерывания в лямбде срабатывает return и, следовательно, поток завершается. Вызов interruptable.interrupt() (строка 4) триггерит завершение потока. Аналогичный вызов nonInterruptable.interrupt() не сработает для потока nonInterruptable, который, как мы видим, продолжает свое выполнение.

Вот более подробная информация о токенах прерывания (interrupt tokens), присоединяющихся потоках и условных переменных.

Токены прерывания

Токен прерывания std::interrupt_token моделирует совместное владение (shared ownership) и может использоваться для сигнализирования о прерывании, если токен валиден. Он предоставляет три метода: valid, is_interrupted, и interrupt.

itoken.valid() true, если токен прерывания может быть использован для сигнализировании о прерывании

itoken.is_interrupted() true, если был инициализирован с true или был вызван метода interrupt()

itoken.interrupt() если !valid() или is_interrupted(), то вызов метода не возымеет эффекта. В противном случае, сигнализирует о прерывании посредством itoken.is_interrupted() == true. Возвращает значение is_interrupted()

Если токен прерывания должен быть временно отключен, вы можете заменить его дефолтным токеном. Дефолтный токен не валиден. Следующий фрагмент кода демонстрирует, как включать и отключать возможность потока принимать сигналы.

std::jthread jthr([](std::interrupt_token itoken){    ...    std::interrupt_token interruptDisabled;     std::swap(itoken, interruptDisabled);     // (1)           ...    std::swap(itoken, interruptDisabled);     // (2)    ...}

std::interrupt_token interruptDisabled не валиден. Это означает, что поток не может принять прерывание между строками (1) и (2), но после строки (2) уже может.

Присоединение потоков

std::jhread представляет собой std::thread с дополнительным функционалом, реализующим сигнализирование о прерывании и автоматическое присоединение. Для поддержки этой функциональности у него есть std::interrupt_token.

Новые перегрузки Wait для условных переменных

Две вариации wait wait_for и wait_until из std::condition_variable получат новые перегрузки. Они принимают std::interrupt_token.

template <class Predicate>bool wait_until(unique_lock<mutex>& lock,                 Predicate pred,                 interrupt_token itoken);template <class Rep, class Period, class Predicate>bool wait_for(unique_lock<mutex>& lock,               const chrono::duration<Rep, Period>& rel_time,               Predicate pred,               interrupt_token itoken);template <class Clock, class Duration, class Predicate>bool wait_until(unique_lock<mutex>& lock,                 const chrono::time_point<Clock, Duration>& abs_time,                 Predicate pred,                 interrupt_token itoken);

Новые перегрузки требует предикат. Эти версии гарантированно получают уведомления, если поступает сигнал о прерывании для переданного им std::interrupt_token itoken. После вызовов wait вы можете проверить, не произошло ли прерывание.

cv.wait_until(lock, predicate, itoken);if (itoken.is_interrupted()){    // interrupt occurred}

Что дальше?

Как я и обещал в своей последней статье, следующая статья будет посвящена оставшимся правилам определения концептов (concepts).


Узнать подробнее о курсе "C++ Developer. Professional".

Смотреть запись демо-занятия по теме
Области видимости и невидимости: участники вместе с экспертом попробовали реализовать класс общего назначения и запустить несколько unit-тестов с использованием googletest.

Подробнее..

Утраченный потенциал подсистемы Windows для Linux (WSL)

06.01.2021 10:12:06 | Автор: admin


Если вы несколько лет вообще не следили за Windows 10 и не знаете, что происходит, то пропустили одну вещь очень горячей темой для разработчиков стала подсистема Windows для Linux, она же WSL. Среди программистов очень часто её обсуждают. Действительно, потрясающе интересная штука.

Наконец-то у нас появилась возможность запустить свой инструментарий Linux на Windows наравне с виндовыми программами. А это значит, что больше не нужно изучать странный PowerShell или пользоваться архаичной консолью CMD.EXE.

К сожалению, не всё так радужно. WSL по-прежнему является неким инородным элементом, который отделён от родной среды Windows. В частности, не может взаимодействовать с родными инструментами Windows.

А ведь изначально всё задумывалось совсем иначе, пишет Джулио Мерино (Julio Merino), автор блога для разработчиков jmmv.dev. Подсистема должна была стать совсем другой, но фактически вышел провал, в каком-то смысле.

Чтобы понять причины этого провала, нужно сначала понять различия между WSL 1 и WSL 2 и как переход на WSL 2 закрыл некоторые интересные перспективы.

Обзор архитектуры WSL 1


Давайте сначала взглянем на WSL 1, и первым делом на её странное название. Почему эта функция называется подсистемой Windows для Linux? Разве не наоборот? Это же не подсистема в Linux, которая делает что-то связанное с Windows, а именно наоборот! То есть грамотно она должна называться Подсистема с функциональностью Linux для Windows или Подсистема Linux для Windows или LSW. Откуда путаница?

Есть мнение, что Microsoft была вынуждена выбрать название наоборот, чтобы избежать упоминания чужих торговых марок. Вот слова Рича Тёрнера (Rich Turner), проект-менеджера WSL:


Что ж с другой стороны, такое странное название технически можно считать корректным, если внимательно изучить архитектуру ядра Windows NT. На странице Архитектура Windows NT в Википедии мы находим следующее:



Пользовательский режим Windows NT состоит из подсистем, передающих запросы ввода-вывода соответствующему драйверу режима ядра посредством менеджера ввода-вывода. Есть две подсистемы на уровне пользователя: подсистемы окружения (запускают приложения, написанные для разных операционных систем) и интегрированные или внутренние подсистемы (управляет особыми системными функциями от имени подсистемы окружения). Режим ядра имеет полный доступ к аппаратной части и системным ресурсам компьютера.

Windows NT разработана с нуля для поддержки процессов, запущенных из множества операционных систем, а Win32 был просто одной из этих подсистем окружения. На такой платформе WSL 1 предоставляет новую подсистему окружения, подсистему Linux, для запуска бинарников Linux поверх ядра Windows NT. У подсистем окружения Win32 и Linux должна быть одна общая интегральная подсистема.

Что всё это на самом деле значит?

Разные системы называют фронтендами вот что это значит. Процесс пользовательского пространства это набор двоичных команд, которые процессор выполняет непрерывно (оставим в стороне прерывания). Ядро операционной системы не знает о том, что делает процесс, пока процесс не выдаст системный вызов: в этот момент ядро восстанавливает контроль для выполнения операции от имени пользователя, которая может быть чем-то вроде чтения файла или паузы на несколько секунд.

Способ, которым процесс выполняет системные вызовы, и семантика этих системных вызовов специфичны для операционной системы. Например, на старых х86 это выглядит так: открытие файла в системе Win32 системный вызов 17h, который инициируется через прерывание INT 2EH, а при открытии файла в системе Linux это системный вызов 5h, который инициируется через прерывание INT 80х.

Но концептуально открытие файла это открытие файла, верно? Нам особенно не интересно, что номера системных вызовов или номера прерываний отличаются друг от друга. И в этом заключается ключевой аспект дизайна WSL 1: подсистема Linux в ядре NT это, проще говоря, реализация уровня системных вызовов Linux перед ядром NT. Эти системные вызовы позже делегируются примитивам NT, а не вызовам Win32. Самое главное: нет никакого перевода с системных вызовов Linux на системные вызовы Win32.

В каком-то смысле это подвиг архитектурной мысли и реальной программной разработки, учитывая в целом хорошую поддержку Linux-приложений под WSL 1 и помня о множестве реальных внутренних отличий NT от Unix, когда Windows вообще нативно не воспринимает стандартную юниксовую логику forkexec.

Истинная красота этой конструкции заключается в том, что на машине работает одно ядро, и это ядро имеет целостное представление обо всех процессах под ним. Ядро знает всё о процессах Win32 и Linux. И все эти процессы взаимодействуют с общими ресурсами, такими как единый сетевой стек, единый менеджер памяти и единый планировщик процессов.

Причины для создания WSL 2


Если WSL 1 так крут, то зачем нужен WSL 2? Здесь две причины:

  • WSL 1 должен, по сути, реализовать все двоичные интерфейсы приложений (ABI) ядра Linux, как говорится, бит к биту. Если в интерфейсе есть ошибка, WSL 1 должен её воспроизвести. А если есть функция, которую трудно представить в ядре NT, то она либо не может быть реализована, либо нуждается в дополнительной логике ядра (и поэтому становится медленнее).


    Уровни и интерфейсы между ними: API и ABI. Высокоуровневое сравнение

  • Подсистема Linux в WSL 1 должна соблюдать любые ограничения и внутренние различия, существующие между ядром NT и традиционным дизайном Unix. Наиболее очевидным отличием является файловая система NTFS и её семантика, а также то, как эти различия вредят производительности бинарных файлов Linux. Низкая производительность файловой системы, видимо, была распространённой жалобой при использовании WSL 1.

WSL 2 выбрасывает всю часть подсистемы Linux и заменяет её полноценной (но очень хорошо скрытой и быстрой) виртуальной машиной. Затем виртуальная машина внутри себя запускает обычное ядро Linux, правильную файловую систему Linux и стандартный сетевой стек Linux. Всё это работает внутри VM.

Это означает, что красота дизайна WSL 1 исчезла: ядро Windows NT больше не видит ничего в мире Linux. Просто большой чёрный ящик, который делает что-то неизвестное внутри себя. Ядро Windows NT видит только точки подключения VMENTER и VMEXIT для виртуальной машины и запросы чтения/записи на уровне блоков на виртуальном диске. Это самое ядро NT теперь ничего не знает о процессах Linux и доступе к файлам. Точно так же работающее ядро Linux ничего не знает об NT.

О некоторых других различиях можно прочитать в официальной документации.

Потерянный потенциал


С точки зрения пользователя, подсистема WSL 2 выглядит лучше: действительно, приложения Linux теперь работают намного быстрее, потому что не проходят через неудобную эмуляцию системных вызовов Linux в ядре NT. Если NTFS трудно использовать с семантикой Linux, то теперь такой проблемы нет, потому что теперь окружение Linux использует ext4 на своём виртуальном диске. И поддержка приложений Linux может быть гораздо более полной, потому что ну, потому что WSL 2 это и есть полноценный Linux.

Но подумайте, за счёт чего достигнуто это удобство? Чего мы лишились?

Какой должна была стать WSL, если бы всё работало так, как задумано:

  • Представьте, как здорово набрать ps или top в сеансе WSL и увидеть рядом процессы Linux и Windows, причём любой из них можно убить командой kill?
  • Представьте, как здорово манипулировать службами Windows из сеанса WSL?
  • Как здорово использовать ifconfig (аналог ipconfig из Windows, хотя есть ещё ip) в рамках сеанса WSL для проверки и изменения сетевых интерфейсов компьютера?


  • По сути, можете представить выполнение абсолютно всех задач системного администрирования в Windows из линуксовой консоли WSL? Это же сказка!

Хотя такого никогда не существовало, такой мир вполне можно себе представить и это могла обеспечить только архитектура WSL 1. И это вовсе не фантазии, потому что именно такую модель использует macOS (хотя это немного читерство, ведь macOS, по сути, является Unix).

Вот что бесит сильнее всего, пишет Джулио Мерино: Хотя я могу установить WSL на свою машину разработки для Azure, но никак не могу его использовать вообще ни для чего. По-прежнему приходится работать в CMD.EXE, поскольку здесь происходит взаимодействие с нативными процессами и ресурсами Windows, а инструменты, с которыми я имею дело, предназначены только для Windows.

На странице вопросов и ответов написано, что WSL 1 не будет заброшен, то есть можно запускать дистрибутивы WSL 1 и WSL 2 вместе, проапгрейдить любой дистрибутив на WSL 2 в любое время или вернуться на WSL 1. И если посмотреть на строгую приверженность к обратной совместимости Microsoft, из-за которой мы до сих пор вынуждены пользоваться архаичными инструментами и протоколами, это может быть правдой. Но поддержка WSL 1 колоссальное усилие, потому что придётся отслеживать и соответствовать всем изменениям Linux. Как бы то ни было, будем надеяться, что WSL 1 продолжит своё существование. И кто знает, вдруг когда-нибудь всё-таки воплотится в жизнь тот волшебный мир, который мы представляем?

Уровень совместимости в BSD


Интересно, что семейство операционных систем BSD ( FreeBSD, OpenBSD, NetBSD, MidnightBSD, GhostBSD, Darwin, DragonFly BSD) всегда отставали от Linux и других коммерческих операционных систем. Но у них очень давно была реализована эта совместимость на бинарном уровне, о которой мы говорим. Джулио Мерино говорит, что совместимость с Linux в ядре NetBSD была реализована ещё в 1995 году, то есть четверть века назад и за 21 год до рождения WSL 1.

И самое замечательное, эта совместимость не ограничивается только Linux. На протяжении многих лет NetBSD поддерживала эмуляцию различных операционных систем. Поддержка SVR4 появилась в 1994 году, и в течение короткого периода времени NetBSD даже поддерживала бинарные файлы PE/COFF да, правильно, это бинарные файлы Win32! Таким образом, в некотором смысле NetBSD реализовала модель WSL 1 наоборот: она позволяла запускать бинарные файлы Win32 поверх ядра NetBSD ещё в 2002 году. Вот так вот.



На правах рекламы


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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru