Перейти к содержимому

Выберите ситуации в которых оправдано использование цикла

  • автор:

Циклы в программировании. Цикл while

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

Цикл while

«While» переводится с английского как «пока». Но не в смысле «до свидания», а в смысле «пока имеем это, делаем то».

Можно сказать, while является универсальным циклом. Он присутствует во всех языках, поддерживающих структурное программирование, в том числе в Python. Его синтаксис обобщенно для всех языков можно выразить так:

while логическое_выражение

Это похоже на условный оператор if . Однако в случае циклических операторов их тела могут выполняться далеко не один раз. В случае if , если логическое выражение в заголовке возвращает истину, то тело выполняется единожды. После этого поток выполнения программы возвращается в основную ветку и выполняет следующие выражения, расположенные ниже всей конструкции условного оператора.

В случае while , после того как его тело выполнено, поток возвращается к заголовку цикла и снова проверяет условие. Если логическое выражение возвращает истину, то тело снова выполняется. Потом снова возвращаемся к заголовку и так далее.

Цикл завершает свою работу только тогда, когда логическое выражение в заголовке возвращает ложь, то есть условие выполнения цикла больше не соблюдается. После этого поток выполнения перемещается к выражениям, расположенным ниже всего цикла. Говорят, «происходит выход из цикла».

Рассмотрите блок-схему цикла while .

Блок-схема цикла while

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

С циклом while возможны две исключительные ситуации:

  • Если при первом заходе в цикл логическое выражение возвращает False , то тело цикла не выполняется ни разу. Эту ситуацию можно считать нормальной, так как при определенных условиях логика программы может предполагать отсутствие необходимости в выполнении выражений тела цикла.
  • Если логическое выражение в заголовке while никогда не возвращает False , а всегда остается равным True , то цикл никогда не завершится, если только в его теле нет оператора принудительного выхода из цикла ( break ) или вызовов функций выхода из программы – quit() , exit() в случае Python. Если цикл повторяется и повторяется бесконечное количество раз, то в программе происходит зацикливание. В это время она зависает и самостоятельно завершиться не может.

Вспомним наш пример из урока про исключения. Пользователь должен ввести целое число. Поскольку функция input() возвращает строку, то программный код должен преобразовать введенное к целочисленному типу с помощью функции int() . Однако, если были введены символы, не являющиеся цифрами, то возникает исключение ValueError , которое обрабатывается веткой except . На этом программа завершается.

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

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

n = input("Введите целое число: ") while type(n) != int: try: n = int(n) except ValueError: print("Неправильно ввели!") n = input("Введите целое число: ") if n % 2 == 0: print("Четное") else: print("Нечетное")

Примечание 1. Не забываем, в языке программирования Python в конце заголовков сложных инструкций ставится двоеточие.

Примечание 2. В выражении type(n) != int с помощью функции type() проверяется тип переменной n . Если он не равен int , то есть значение n не является целым числом, а является в данном случае строкой, то выражение возвращает истину. Если же тип n равен int , то данное логическое выражение возвращает ложь.

Примечание 3. Оператор % в языке Python используется для нахождения остатка от деления. Так, если число четное, то оно без остатка делится на 2, то есть остаток будет равен нулю. Если число нечетное, то остаток будет равен единице.

Проследим алгоритм выполнения этого кода. Пользователь вводит данные, они имеют строковый тип и присваиваются переменной n . В заголовке while проверяется тип n . При первом входе в цикл тип n всегда строковый, то есть он не равен int . Следовательно, логическое выражение возвращает истину, что позволяет зайти в тело цикла.

Здесь в ветке try совершается попытка преобразования строки к целочисленному типу. Если она была удачной, то ветка except пропускается, и поток выполнения снова возвращается к заголовку while .

Теперь n связана с целым числом, следовательно, ее тип int , который не может быть не равен int . Он ему равен. Таким образом логическое выражение type(n) != int возвращает False , и весь цикл завершает свою работу. Далее поток выполнения переходит к оператору if-else, находящемуся в основной ветке программы. Здесь могло бы находиться что угодно, не обязательно условный оператор.

Вернемся назад. Если в теле try попытка преобразования к числу была неудачной, и было выброшено исключение ValueError , то поток выполнения программы отправляется в ветку except и выполняет находящиеся здесь выражения, последнее из которых просит пользователя снова ввести данные. Переменная n теперь имеет новое значение.

После завершения except снова проверяется логическое выражение в заголовке цикла. Оно даст True , так как значение n по-прежнему строка.

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

Рассмотрим следующий пример:

total = 100 i = 0 while i  5: n = int(input()) total = total - n i = i + 1 print("Осталось", total)

Сколько раз «прокрутится» цикл в этой программе, то есть сколько итераций он сделает? Ответ: 5.

  1. Сначала переменная i равна 0. В заголовке цикла проверяется условие i < 5 , и оно истинно. Тело цикла выполняется. В нем меняется значение i , путем добавления к нему единицы.
  2. Теперь переменная i равна 1. Это меньше пяти, и тело цикла выполняется второй раз. В нем i меняется, ее новое значение 2.
  3. Два меньше пяти. Тело цикла выполняется третий раз. Значение i становится равным трем.
  4. Три меньше пяти. На этой итерации i присваивается 4.
  5. Четыре по прежнему меньше пяти. К i добавляется единица, и теперь ее значение равно пяти.

«Смысловая нагрузка» данного цикла – это последовательное вычитание из переменной total вводимых чисел. Переменная i в данном случае играет только роль счетчика итераций цикла. В других языках программирования для таких случаев предусмотрен цикл for , который так и называется: «цикл со счетчиком». Его преимущество заключается в том, что в теле цикла не надо изменять переменную-счетчик, ее значение меняется автоматически в заголовке for .

В языке Python тоже есть цикл for . Но это не цикл со счетчиком. В Питоне он предназначен для перебора элементов последовательностей и других сложных объектов. Данный цикл и последовательности будут изучены в последующих уроках.

Для while наличие счетчика не обязательно. Представим, что надо вводить числа, пока переменная total больше нуля. Тогда код будет выглядеть так:

total = 100 while total > 0: n = int(input()) total = total - n print("Ресурс исчерпан")

Сколько раз здесь выполнится цикл? Неизвестно, все зависит от вводимых значений. Поэтому у цикла со счетчиком известно количество итераций, а у цикла без счетчика – нет.

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

Примечание 1. Не обязательно в выражениях total = total — n и i = i + 1 повторять одну и ту же переменную. В Python допустим сокращенный способ записи подобных выражений: total -= n и i += 1 .

Примечание 2. При использовании счетчика он не обязательно должен увеличиваться на единицу, а может изменяться в любую сторону на любое значение. Например, если надо вывести числа кратные пяти от 100 до 0, то изменение счетчика будет таким i = i — 5 , или i -= 5 .

Примечание 3. Для счетчика не обязательно использовать переменную с идентификатором i . Можно назвать переменную-счетчик как угодно. Однако так принято в программировании, что счетчики обозначают именами i и j (иногда одновременно требуются два счетчика).

Практическая работа

  1. Измените последний код из урока так, чтобы переменная total не могла уйти в минус. Например, после предыдущих вычитаний ее значение стало равным 25. Пользователь вводит число 30. Однако программа не выполняет вычитание, а выводит сообщение о недопустимости операции, после чего осуществляет выход из цикла.
  2. Используя цикл while , выведите на экран для числа 2 его степени от 0 до 20. Возведение в степень в Python обозначается как ** . Фрагмент вывода:
. 32 64 128 256 512 1024 .

Примеры решения и дополнительные уроки в pdf-версии курса

X Скрыть Наверх

Python. Введение в программирование

20 типичных ошибок многопоточности в C++

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

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

Все примеры успешно компилируются и исполняются в Ubuntu 16.04 LTS:

 
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

#1 Отсутствие join() или detach() перед завершением

Если забыть вызвать join() или detach() перед завершением программы, это может привести к её аварийному завершению.

 
#include #include void foo()  std::cout  <"foo"  <std::endl; > int main(int argc, char *argv[])  std::thread t(foo); return 0; > 

В конце функции main() объект t выходит из области видимости и вызывается деструктор. Внутри деструктора выполняется проверка на подключаемость потока. Подключаемый поток - это поток который может или уже выполняется. В данном случае это именно так поэтому будет вызвана функция std::terminate() .

В зависимости желаемого поведения следует либо подождать завершения потока:

 
int main(int argc, char *argv[])  std::thread t(foo); t.join(); return 0; > 

либо разорвать с ним связь

 
int main(int argc, char *argv[])  std::thread t(foo); t.detach(); return 0; > 

#2 Попытка дождаться завершения неподключаемого потока

Для объектов std::thread которые были перемещены, завершены join() или брошены detach() нельзя дождаться завершения.

 
#include #include void foo()  std::cout  <"foo"  <std::endl; > int main(int argc, char *argv[])  std::thread t(foo); t.detach(); // . какая-то логика . t.join(); return 0; > 

В таких случаях следует проверять, а можно ли в принципе подключить поток, и только потом уже вызывать join() .

 
if (t.joinable())  t.join(); > 

#3 Вызов join() блокирует вызывающий поток

В реальных приложениях потоки могут обслуживать достаточно длительные операции связанные с сетевым вводом/выводом или реакцией пользователя в пользовательском интерфейсе. Попытка дождаться завершения таких потоков в основном потоке или в потоке отвечающим за интерфейс может привести к "заморозке". Лучше найти другое решение.

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

#4 Не учитывать особенности передачи аргументов в поток

Аргументы в функцию потока перемещаются или копируются по значению. Пример ниже даже не скомпилируется.

 
#include #include void foo(int &s)  s = 42; > int main(int argc, char *argv[])  int answer = 0; std::thread t(foo, answer); t.join(); return 0; > 

Для успешной компиляции ссылка должна быть передана через std::ref

 
std::thread t(foo, std::ref(answer)); 

#5 Игнорировать общий доступ к ресурсам

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

В качестве примера рассмотрим вывод в консоль в несколько потоков - основной и шесть дополнительных.

 
#include #include void foo(const std::string &message)  std::cout  <"thread "  <std::this_thread::get_id()  <", message "  <message  <std::endl; > int main(int argc, char *argv[])  std::thread t1(foo, "каждый"); std::thread t2(foo, "охотник"); std::thread t3(foo, "желает"); foo("знать"); std::thread t4(foo, "где"); std::thread t5(foo, "сидит"); std::thread t6(foo, "фазан"); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join(); return 0; > 

В результате получим мешанину из слов:

 
thread thread thread 140597982512960, message знать140597965162240 thread 140597939984128, message где thread 140597931591424, message 140597956769536, message thread каждый140597948376832, message желает, message сидит охотник thread 140597923198720, message фазан

Дело в том, что консоль одна, а семь потоков пытаются выводить на неё одновременно. Чтобы сделать вывод более предсказуемым необходимо ограничить одновременный доступ. Сделаем это через std::mutex - заблокируем до вывода и освободим после.

 
#include std::mutex cout_guard; void foo(const std::string &message)  cout_guard.lock(); std::cout  <"thread "  <std::this_thread::get_id()  <", message "  <message  <std::endl; cout_guard.unlock(); > 

Вывод получится читаемый:

 
thread 140710040659776, message знать thread 140710023309056, message каждый thread 140710006523648, message желает thread 140709989738240, message сидит thread 140709981345536, message фазан thread 140710014916352, message охотник thread 140709998130944, message где

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

#6 Забыть вызвать unlock()

В предыдущем примере для разделения доступа к ресурсу использовался std::mutex . Это не самый удачный способ, поскольку вызова unlock() мы можем и не достичь, если вообще не забыли его вызвать.

 
void foo(const std::string &message)  cout_guard.lock(); std::cout  <"thread "  <std::this_thread::get_id()  <", message "  <message  <std::endl; // cout_guard.unlock(); > 

После появления первого же сообщения программа зависнет:

 
thread 140569688782656, message знать

Чтобы защититься от ошибок такого рода воспользуемся std::lock_guard , который манипулирует временем жизни блокировки в стиле RAII.

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

 
void foo(const std::string &message)  std::lock_guardstd::mutex> lock(cout_guard); std::cout  <"thread "  <std::this_thread::get_id()  <", message "  <message  <std::endl; > 

#7 Пренебрежение размером защищённой секции

Пока мы находимся внутри защищённой секции все остальные потоки рвущиеся её выполнить заблокированы. Старайтесь делать заблокированный участок как можно меньше.

 
#include #include #include #include using namespace std::chrono_literals; std::mutex cout_mutex; void foo()  std::lock_guardstd::mutex> lock(cout_mutex); std::this_thread::sleep_for(1s); // на самом деле что-то очень полезное и безопасное std::cout  <"foo"  <std::endl; > int main(int argc, char *argv[])  std::thread t1(foo); std::thread t2(foo); t1.join(); t2.join(); return 0; > 

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

 
void foo()  std::this_thread::sleep_for(1s); // на самом деле что-то очень полезное и безопасное std::lock_guardstd::mutex> lock(cout_mutex); std::cout  <"foo"  <std::endl; > 

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

#8 Взаимные блокировки

Как правило такие блокировки уже навсегда из-за чего получили название deadlock. Типичная ситуация такой блокировки представлена ниже. Функция sleep_for даёт нам 100% шанс попасть в вечную блокировку, без неё скорее всего на любой машине этот код выполнился бы без зависания.

 
#include #include #include #include using namespace std::chrono_literals; std::mutex cerr_mutex; std::mutex cout_mutex; void foo()  cerr_mutex.lock(); std::cerr  <"use cerr in foo"  <std::endl; std::this_thread::sleep_for(1s); cout_mutex.lock(); std::cout  <"use cout in foo"  <std::endl; cout_mutex.unlock(); cerr_mutex.unlock(); > void bar()  cout_mutex.lock(); std::cout  <"use cout in bar"  <std::endl; std::this_thread::sleep_for(1s); cerr_mutex.lock(); std::cerr  <"use cerr in bar"  <std::endl; cerr_mutex.unlock(); cout_mutex.unlock(); > int main(int argc, char *argv[])  std::thread t1(foo); std::thread t2(bar); t1.join(); t2.join(); return 0; > 

Причина зависания кроется в перекрёстной блокировке. Когда оба потока начинают работать каждый из них блокирует свой mutex. То есть t1 захватил cerr_mutex, а t2 - cout_mutex. После вызова sleep_for потоки пытаются захватить их наоборот, еще не освободив занятые. Для того чтобы освободить свой mutex потоку приходится ждать пока это сделает второй, а у второго ситуация ровно такая же.

Самое простое решение использовать std::lock для захвата обоих mutex.

 
void foo()  std::lock(cerr_mutex, cout_mutex); std::cerr  <"use cerr in foo"  <std::endl; std::this_thread::sleep_for(1s); std::cout  <"use cout in foo"  <std::endl; cout_mutex.unlock(); cerr_mutex.unlock(); > 

Если условия позволяют, можно использовать std::timed_mutex . Для выхода из перекрёстной блокировки достаточно, чтобы одну из них можно было нарушить.

 
void foo()  cerr_mutex.lock(); std::cerr  <"use cerr in foo"  <std::endl; std::this_thread::sleep_for(1s); if (cout_mutex.try_lock_for(1s))  std::cout  <"use cout in foo"  <std::endl; cout_mutex.unlock(); > cerr_mutex.unlock(); > 

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

#9 Повторный захват std::mutex

Эта некоторая вариация на тему перекрёстного захвата с тем лишь отличием, что для такой блокировки достаточно одного std::mutex. Даже дополнительные потоки не нужны. Тут можно было бы привести в качестве примера страшную банальщину типа:

 
void foo()  std::lock_guardstd::mutex> lock1(cerr_mutex); std::lock_guardstd::mutex> lock2(cerr_mutex); std::cerr  <"foo"  <std::endl; > 

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

 
#include #include std::mutex cerr_mutex; int bar()  std::lock_guardstd::mutex> lock(cerr_mutex); std::cerr  <"bar"  <std::endl; return 42; > void foo()  std::lock_guardstd::mutex> lock(cerr_mutex); std::cerr  <"foo, bar = "  <bar()  <std::endl; > int main(int argc, char *argv[])  foo(); return 0; > 

Вызов foo приводит к захвату mutex, но в процессе вызова bar возникает необходимость блокировки ресурса повторно. Это такой же deadlock, только теперь основной поток ждёт сам себя.

Существует очевидный способ решить проблему - заменить обычный std::mutex на рекурсивный std::recursive_mutex и это решит нашу проблему, но решит её ой каким опасным способом. Тысячу раз подумайте всё ли будет в порядке при таком подходе, не удастся ли найти решение более элегантное

 
void foo()  auto b = bar(); std::lock_guardstd::mutex> lock(cerr_mutex); std::cerr  <"foo, bar = "  <b  <std::endl; > 

#10 Излишняя предосторожность

Когда возникает необходимость модифицировать простые типы наподобие bool или int использование 'std::atomic' почти всегда более эффективно в сравнении с использованием mutex.

 
#include std::mutex counter_mutex; int counter; void foo()  std::lock_guardstd::mutex> lock(counter_mutex); ++counter; > 

Та же самая логика без использования mutex и использованием atomic.

 
#include std::atomicint> counter; void foo()  ++counter; > 

#11 Частое создание потоков без использования пулов

Создание и удаление потоков дорогое удовольствия с точки зрения затрат CPU. Часто создавать и удалять потоки в приложении, которое само по активно занимается вычислениями значит мешать ему. Вместо того, чтобы часто создавать и удалять потоки лучше создать пул предварительно запущенных потоков и распределять между ними задания.

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

Существуют готовые реализации такого рода пулов. Например, TBB

#12 Не обработанные исключения в потоке

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

 
#include #include #include void foo()  throw std::runtime_error("foo"); > int main(int argc, char *argv[])  try  std::thread t(foo); t.join(); > catch (const std::exception &e)  std::cout  <"error"  <e.what()  <std::endl; > return 0; > 

Программа аварийно завершится так и не вызвав обработчик исключения. Решением может быть перехват исключения в потоке и передача информации о нём через экземпляр std::exception_ptr в родительский поток и повторного бросания уже за пределами породившего исключения потока.

 
#include #include #include static std::exception_ptr eptr = nullptr; void foo()  try  throw std::runtime_error("foo"); > catch (. )  eptr = std::current_exception(); > > int main(int argc, char *argv[])  std::thread t(foo); t.join(); if (eptr)  try  std::rethrow_exception(eptr); > catch (const std::exception &e)  std::cout  <"error"  <e.what()  <std::endl; > > return 0; > 

#13 Имитация асинхронной работы без std::async

Когда нужно выполнить часть кода независимо от основного потока отличным выбором будет использование std::async для запуска. Это тоже самое, что создать ещё один поток и передать ему на выполнение функцию или лямбду. Правда при этом за жизненным циклом потока и исключений в нём тоже придётся следить самостоятельно. В случае использования std::async можно не заботиться об этом да ещё и существенно сократить вероятность блокировки.

Еще одно важно преимущество заключается в возможности получить результат работы функции через std::future . Функция int foo() , будучи выполнена как асинхронная задача, заранее установит результат своей работы. А получим мы его тогда, когда нам это будет удобно.

 
#include #include #include int main(int argc, char *argv[])  auto f = std::async(sqrt, 9.0); std::cout  <f.get()  <std::endl; return 0; > 

Использование потоков напрямую делает получение результатов чуть более громоздким. Это может быть:

Передача ссылки на результирующую переменную в поток, в котором необходимо сохранить результат.

 
#include #include #include void foo(double i, double &r)  r = std::sqrt(i); > int main(int argc, char *argv[])  double result = 0.0; auto t = std::thread(foo, 9, std::ref(result)); t.join(); std::cout  <result  <std::endl; return 0; > 

Сохранение результата в переменной класса функционального объекта и чтение его после завершения работы.

 
#include #include #include templatetypename T> class foo  T r; public: T get()  return r; > void operator()(T i)  r = std::sqrt(i); > >; int main(int argc, char *argv[])  auto f = foodouble>(); auto t = std::thread(std::ref(f), 9); t.join(); std::cout  <f.get()  <std::endl; return 0; > 

Курт Гантерот в своей книге утверждает, что создание потоков в 14 раз дороже использования std::async .

Короче говоря, пока не доказано обратное использовать следует std::async .

#14 Опускание std::launch::async когда это действительно необходимо

Название std::async может ввести в заблуждение, потому что функция, которая будет передана для запуска по умолчанию может и не запуститься отдельно от вызываемого потока!

Существует два способа запуска:

  1. std::launch::async . Задача будет немедленно запущена в отдельном потоке.
  2. std::launch::deferred . Выполнение задачи будет отложено до вызова .get() или .wait() возвращаемого объекта std::future . При этом выполнение осуществляется синхронно!

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

Чтобы избежать недоразумений явно указывайте std::launch::async при запуске std::async .

 
auto f = std::async(sqrt, 9.0); 

Гарантировано в другом потоке:

 
auto f = std::async(std::launch::async, sqrt, 9.0); 

#15 Использование .get() может привести к ожиданию

 
#include #include #include int main(int argc, char *argv[])  std::futureint> f = std::async(std::launch::async, []() std::this_thread::sleep_for(std::chrono::seconds(1)); return 42; >); while (true)  // . std::cout  <f.get()  <std::endl; // . > return 0; > 

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

Обе проблемы можно решить проверив future на готовность.

 
if (f.valid())  std::cout  <f.get()  <std::endl; > 

#16 Исключение из задачи перетечёт во future

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

 
#include #include int main(int argc, char *argv[])  std::futureint> f = std::async(std::launch::async, []() throw std::runtime_error("error"); return 42; >); std::cout  <f.get()  <std::endl; return 0; > 

Программа аварийно завершится. Если в задаче было брошено исключение, оно распространится и на вызов .get(). Если до конца жизни future .get() так и не будет вызван исключение просто проигнорируется.

Для таких задач имеет смысл использование обычной конструкции try/catch.

 
try  std::cout  <f.get()  <std::endl; > catch (const std::exception &e)  std::cout  <e.what()  <std::endl; > 

#17 Использование std::async там, где нужен тонкий контроль потоков

В большинстве случаев достаточно использования std::async , кроме ситуаций когда возникает необходимость в полном контроле над исполняемым потоком.

Например изменить параметры для планировщика:

 
#include #include void foo()  std::cout  <"foo" <std::endl; > int main(int argc, char *argv[])  auto t = std::thread(foo); sched_param sch; int policy; pthread_getschedparam(t.native_handle(), &policy, &sch); sch.sched_priority = 20; pthread_setschedparam(t.native_handle(), SCHED_FIFO, &sch); t.join(); return 0; > 

Это возможно благодаря наличию метода .native_handle() у std::thread , значение которого можно использовать в POSIX системах. Использование этого метода полезно всегда, когда не хватает функциональности ни std::async ни std::thread . Использование std::async скрывает детали реализации и непригодно для такой тонкой работы.

#18 Пренебрежение анализом нагрузки на CPU

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

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

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

Я рассмотрел два крайних варианта, но наш конечно же будет посередине. Да, есть метод std::thread::hardware_concurrency() , которая сообщит нам сколько ядер доступно планировщику с учётом физических и логических.

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

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

Если потоки преимущественно заблокированы мьютексами или вводом/выводом, то ограничивать количество потоков ради экономии процессора не имеет большого смысла.

В остальных случаях необходимо нагрузочное тестирование и мониторинг с регулировкой количества потоков. Иными словами подбирается экспериментально.

#19 Использование квалификатора volatile для синхронизации

Использование этого квалификатора указание компилятору того, что изменения объекта могут происходить без контроля на этапе компиляции. Не в том смысле, что они происходят из разных потоков, а в том, что вообще за пределами кода, грубо говоря самопроизвольно. Это очень низкоуровневое указание и это никак не помогает в одновременном доступе внутри процесса.

Для синхронизации следует использовать atomic , mutex , и condition_variable .

#20 Неоправданное использование lock-free алгоритмов

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

Прежде чем приступить к использованию свободных от блокировок алгоритмов следует подумать над тремя вопросами:

  1. Пробовали ли спроектировать код без необходимости синхронизации?
  2. Выполняли ли анализ производительности, поиск и оптимизацию узких мест?
  3. Можно ли ограничиться горизонтальным масштабированием?

Использование lock-free алгоритмов оправдано, когда никаких других решений просто не осталось.

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

Образовательная концепция "Использование ИКТ на уроках естественного цикла"

Использование ИКТ на уроках естественного цикла Подготовлено участником конку.

1 слайд Использование ИКТ на уроках естественного цикла Подготовлено участником конкурса «Молодой педагог года – 2010» учителем биологии, химии и информатики II категории Красняк Е. В. МОУ «СОШ № 29» Партизанского городского округа г. Партизанск - 2010

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

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

В изучении школьного курса географии и биологии выделяю несколько основных н.

3 слайд В изучении школьного курса географии и биологии выделяю несколько основных направлений, где оправдано использование компьютера: наглядное представление объектов и явлений микромира; изучение различных производств (в курсе географии); моделирование биологического эксперимента и химических реакций; система тестового контроля; подготовка к государственной итоговой аттестации

Знает каждый до полной ясности, Что опасно в лаборатории: Чуть ошибся, и в кр.

6 слайд Знает каждый до полной ясности, Что опасно в лаборатории: Чуть ошибся, и в крематории. Ну, а здесь ты как в санатории, Это техника безопасности! …разнообразит формы работы на уроке за счет одновременного использования иллюстративного, статистического, методического, а также аудио- и видеоматериала. Осуществляться на разных этапах урока как способ создания проблемной ситуации, как способ объяснения нового материала, как форма закрепления изученного, как форма проверки домашнего задания, Как виртуальная лабораторная работа.

- форма подачи материала в виде слайдов, на которых могут быть представлены.

7 слайд - форма подачи материала в виде слайдов, на которых могут быть представлены таблицы, схемы, рисунки, иллюстрации, аудио- и видеоматериалы. Возможности презентаций: 1. Демонстрация фильмов, анимации. 2. Выделение (нужной области). 3. Последовательность шагов. 4. Интерактивность. 5. Движение объектов. 6. Моделирование.

Урок-лекция Урок анализа текста Обобщающие уроки Урок -презентация

8 слайд Урок-лекция Урок анализа текста Обобщающие уроки Урок -презентация

сохраняет структуру общеобразовательного цикла, полностью соответствует требо.

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

экономию времени на уроке; глубину погружения в материал; повышенную мотиваци.

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

1. Реализуются новые цели образования: организация самостоятельной прод.

13 слайд 1. Реализуются новые цели образования: организация самостоятельной продуктивной деятельности; формирование информационной грамотности и компетентности; индивидуализация процесса; ценностно-смысловое определение учащихся. 2. Повышается эффективность познавательной деятельности учащихся за счет расширения возможностей доступа к образовательной информации; совершенствования организационных форм и методов обучения, воспитания; формирования умения самостоятельно приобретать знания; визуализации представленной информации; ориентации на развитие интеллектуального потенциала обучающихся; развития творческого потенциала учащихся; незамедлительной обратной связи; одновременного использования нескольких каналов восприятия учащихся. 3. Педагоги, использующие наработанные материалы, освобождаются от выполнения трудоемкой рутинной работы при подготовке к занятию и проверке знаний учащихся.

Повышение качества образования; Повышение познавательного интереса к предмету.

14 слайд Повышение качества образования; Повышение познавательного интереса к предмету; Повышение уровня информационной культуры; Увеличение доли самостоятельной продуктивной деятельности учащихся на уроке.

Удаление маточной трубы (тубэктомия)

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

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

Показания к удалению маточных труб

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

Ситуации, при которых оправдано удаление маточной трубы в Москве (тубэктомия):

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

Противопоказания

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

Удаление маточной трубы (тубэктомия) невозможно в следующих случаях:

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

Виды

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

Способы проведения операции:

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

Односторонняя.

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

Двухсторонняя.

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

Оперирующие гинекологи

Кононов Станислав Николаевич
стаж: 18 лет
Флагманский медицинский центр «Рязанский проспект»

Покаленьева Мария Шамилевна
стаж: 13 лет
Флагманский медицинский центр «Рязанский проспект»

Кононов Станислав Николаевич
стаж: 18 лет
Флагманский медицинский центр «Рязанский проспект»

Покаленьева Мария Шамилевна
стаж: 13 лет
Флагманский медицинский центр «Рязанский проспект»

Подготовка к удалению маточных труб (тубэктомии)

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

Перед операцией по удалению маточной трубы пациентка должна выполнить следующие правила:

  • планировать лечение в первой половине менструального цикла;
  • соблюдать диетическое питание на протяжении нескольких дней до операции;
  • выполнить очищение кишечника накануне удаления маточной трубы;
  • перестать есть за 10 часов до манипуляции;
  • полностью отказаться от жидкости за 3–4 часа до оперативного лечения;
  • сбрить волосы на животе и половых органах в дату записи.

Обследование перед удалением маточной трубы включает:

  • Общий анализ крови
  • Анализ свертывания крови
  • Общий анализ мочи
  • Определение группы крови
  • Анализы крови на антитела
  • Рентген органов грудной клетки
  • ЭКГ
  • УЗИ органов малого таза
  • Мазок на флору
  • Магнитно-резонансная томография
  • Прием гинеколога
  • Консультация анестезиолога
  • Терапевт

Подготовка к удалению маточных труб (тубэктомии)

Проведение удаления маточных труб в «Будь-Здоров»

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

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

Удаление маточной трубы (тубэктомия) совершается в операционной с соблюдением стерильности. Во время операции обязательно присутствует хирург, анестезиолог и средний медперсонал. Длительность оперативного лечения составляет от 30–40 минут до часа.

Ход операции тубэктомии лапароскопическим способом:

Женщина ложится на стол в операционной.
Анестезиолог выполняет анестезию.
Хирург обрабатывает живот антисептиком и определяет место будущего расположения оборудования.

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

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

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

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

Хирург вынимает инструмент, на место разрезов накладывает швы и закрывает стерильной повязкой.
Женщина отправляется в послеоперационное отделение для восстановления.

Результат после удаления маточных труб

Результат после операции по удалению маточной трубы и возможные осложнения после удаления маточной трубы

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

К возможным осложнениям после тубэктомии относят:

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

Восстановление после удаления маточных труб

Реабилитация после удаления маточной трубы (тубэктомии)

После выполненной операции начинается восстановительный период. Лапароскопия позволяет снизить продолжительность реабилитации и быстрее вернуться к обычному укладу жизни.

Реабилитационный период включает:

  • нахождение в стационаре на протяжении 2–7 дней;
  • выполнение регулярных перевязок под контролем врача и снятие швов на 7–10 сутки;
  • полный покой в течение первых суток после тубэктомии;
  • ограничение физических нагрузок, занятий спортивными видами деятельности в течение 2–3 недель;
  • употребление легкоусвояемых блюд в первые дни после оперативного лечения с последующим расширением рациона;
  • запрет на интимную близость до 1 месяца;
  • прием лекарств, назначенных доктором;
  • отказ от бань, горячих ванн, саун, бассейнов, соляриев в течение всего периода реабилитации;
  • использование компрессионного трикотажа, бандажа на срок до двух месяцев;
  • регулярный визит к гинекологу и соблюдение его рекомендаций;
  • длительный прием контрацептивов и планирование беременности не ранее чем через 12 месяцев после операции.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *