Глава 1 Базовый Синтаксис ; День 36 ; 36.2

С 0 до Senior
18 May 202420:56

Summary

TLDRВ этом видео учитель Сергей продолжает курс по многопоточному программированию на C++. Он объясняет, как использовать мьютексы для синхронизации потоков и защиты разделяемых данных. В видео рассматривается проблема совместного использования ресурсов и как с ее помощью можно решить с помощью мьютексов. Также Сергей вводит Lock Guard, который автоматически захватывает и освобождает мьютекс, предотвращая возможные ошибки. Он демонстрирует, как правильно разделять синхронизованный и асинхронный код, чтобы достичь оптимального использования многопоточности и улучшить производительность программы.

Takeaways

  • 🔒 В видео рассматривается проблема синхронизации потоков и использование мьютексов для защиты разделяемых ресурсов.
  • 👨‍🏫 В плейлисте есть уроки о потоках и мьютексах, которые рекомендуется изучить для лучшего понимания темы.
  • 📚 Создание мьютекса (MTX) позволяет контролировать доступ к критической секции кода, обеспечивая, что только один поток может работать с ней одновременно.
  • 🔄 Примеры в видео демонстрируют, как несинхронизированные потоки могут привести к некорректному выводу данных, например, на консоль.
  • ⏱️ В видео показано, что использование мьютексов может снизить производительность программы, так как потоки будут ожидать доступ к защищенным ресурсам.
  • 📐 В качестве примера синхронизации потоков используются функции рисования геометрических фигур в консоли.
  • 🔄 Демонстрирован эффективный многопоточный подход, который показывает, как части кода могут выполняться в многопоточном режиме, в то время как критические секции защищены мьютексами.
  • 🛠️ В видео рассматривается использование Lock Guard для автоматической блокировки и разблокировки мьютекса при входе и выходе из области видимости.
  • 👀 Автор подчёркивает важность корректного использования мьютексов и Lock Guard для предотвращения ошибок и проблем с производительностью.
  • 🔑 В заключение, видео подчёркивает преимущества использования библиотеки STL и стандартных шаблонов для эффективного многопоточного программирования.

Q & A

  • Что означает аббревиатура 'МТЭК' в контексте многопоточного программирования?

    -В контексте многопоточного программирования 'МТЭК' означает 'мьютекс' (mutex), который используется для синхронизации потоков и защиты разделяемых данных.

  • Какие проблемы могут возникнуть при работе с несинхронизированными потоками?

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

  • Что такое 'Lock Guard' в многопоточном программировании на C++?

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

  • Какие есть методы для управления мьютексом в объекте типа 'MTX'?

    -В объекте типа 'MTX' используются методы 'lock' и 'unlock' для управления доступом к защищенным ресурсам.

  • Почему важно использовать 'Lock Guard' при работе с мьютексами?

    -Использование 'Lock Guard' важно для автоматического освобождения мьютекса после завершения защищенной секции кода, что предотвращает возникновение ошибок, связанных с забвом освободить мьютекс.

  • Какие есть различия между использованием мьютекса и 'Lock Guard'?

    -Мьютекс используется вручную с явным вызовом методов 'lock' и 'unlock', в то время как 'Lock Guard' управляет блокировкой и разблокировкой автоматически через自己的人生ycle в конструкторе и деструкторе.

  • Чем может быть пример разделяемого ресурса в многопоточном программировании?

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

  • Какие могут быть последствия неправильного использования мьютексов?

    -Неправильное использование мьютексов может привести к нарушению синхронизации потоков, deadlock'ам или race condition, что повлияет на стабильность и корректность программы.

  • Что такое 'ботылочный горлышек' в контексте производительности многопоточности?

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

  • Как 'Lock Guard' может помочь в предотвращении ошибок, связанных с управлением мьютексами?

    -'Lock Guard' может помочь, автоматически захватывая и освобождая мьютекс, что упрощает управление блокировками и уменьшает риск человеческой ошибки.

  • Какие задачи 'Lock Guard' выполняет в многопоточном приложении?

    -'Lock Guard' выполняет задачи автоматического захвата мьютекса при создании объекта и освобождения его при уничтожении объекта, обеспечивая корректное управление доступом к разделяемым ресурсам.

Outlines

00:00

🔒 Многопоточность и синхронизация потоков

В этом параграфе рассматривается проблема синхронизации в многопоточном программировании. Автор объясняет, что для корректной работы потоков и предотвращения неконтролируемой работы с общим ресурсом, таким как консоль, необходимо использовать механизмы синхронизации. В примере кода демонстрируется, как многопоточная программа может работать некорректно без синхронизации, и как применение мьютекса (MTX) с помощью методов lock и unlock может исправить ситуацию. Также упоминается, что использование мьютекса может снизить производительность программы из-за блокировки потоков, что подтверждается экспериментальным временем выполнения кода с и без синхронизации.

05:00

👷‍♂️ Применение мьютекса для защиты разделяемых ресурсов

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

10:01

🛠 Алгоритм equal с бинарным предикатом в STL

В этом параграфе рассматривается использование алгоритма equal из стандартной библиотеки шаблонов (STL) в языке программирования C++. Автор объясняет, как использовать алгоритм equal с бинарным предикатом для сравнения двух коллекций, когда элементы класса не перегружают оператор сравнения. Приводится пример с классом Point и векторами此类对象, где бинарный предикат используется для сравнения объектов по полям X и Y. Также рассматривается поведение алгоритма при изменении порядка элементов и значений полей, а также рекомендуется использование отладчика для изучения работы алгоритма на примере.

15:02

🛡️ Использование Lock Guard для автоматической синхронизации

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

20:02

🔄 Блокировка и разблокировка мьютекса с помощью Lock Guard

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

Mindmap

Keywords

💡мьютекс (mutex)

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

💡многопоточное программирование

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

💡синхронизация потоков

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

💡критическую секцию

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

💡Lock Guard

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

💡разделяемый ресурс

Разделяемый ресурс - это объект или данные, доступ к которым может иметь несколько потоков одновременно. В контексте видео, консоль является разделяемым ресурсом, доступ к которому синхронизируется с помощью мьютекса и Lock Guard для предотвращения нарушения данных.

💡ботылочный горлышек

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

💡алгоритм equal

Алгоритм equal - это функция из стандартной библиотеки C++ STL, используемая для сравнения двух последовательностей на равенство. В видео рассматривается использование алгоритма equal с бинарным предикатом для сравнения объектов класса Point, определяя их равенство по полям.

💡бинарный предикат

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

💡STL (стандартная библиотека шаблонов)

STL (Standard Template Library) - это коллекция классов, функций и алгоритмов в языке программирования C++, предоставляющая универсальные шаблоны для работы с коллекциями данных. В видео рассматривается использование STL для многопоточного программирования и работы с алгоритмами, такими как equal.

Highlights

Введение в многопоточное программирование и создание потоков T1 и T2 для выполнения функции Print.

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

Использование мьютекса (MTX) для синхронизации потоков и предотвращения ошибок вывода.

Объяснение методов lock и unlock для управления доступом потоков к критической секции кода.

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

Демонстрация корректного вывода двух прямоугольников после синхронизации потоков.

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

Влияние синхронизации потоков на производительность программы и время выполнения.

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

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

Анализ производительности многопоточности в зависимости от использования синхронизации и защиты ресурсов.

Пример использования бинарного предиката с алгоритмом equal для сравнения двух последовательностей.

Обсуждение важности корректного сравнения объектов класса без перегрузки оператора ==.

Введение в Lock Guard как класс для автоматической синхронизации потоков с помощью мьютекса.

Объяснение того, как Lock Guard захватывает мьютекс в конструкторе и освобождает в деструкторе.

Раскрытие преимуществ использования Lock Guard для избегания ошибок при синхронизации потоков.

Демонстрация использования Lock Guard для защиты разделяемого ресурса и корректного выполнения программы.

Transcripts

play00:03

и собственно выполним Ту же самую логику

play00:06

но только в двух отдельных потоках У нас

play00:08

есть Т1 Т2 и соответственно мы эти

play00:10

потоки будем Джони к нашему основному

play00:11

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

play00:13

потоками Что такое Joint Что такое детач

play00:14

то советую вам посмотреть первый урок в

play00:16

этом плейлисте ссылку на весь плейлист

play00:17

тоже оставлю внизу описании Ну и

play00:18

собственно сейчас мы всё это дело

play00:19

запускаем в многопоточного режиме у нас

play00:21

по сути есть Майн поток который ничего

play00:22

не будет делать и создаётся ещё два

play00:24

потока каждый из этих потоков будет

play00:25

выполнять функцию Print и отрисовывать

play00:26

наши прямоугольники только символами

play00:28

передадим разные один звёздочки другой

play00:29

реш запускаем смотрим Что у нас

play00:31

получится у нас получилась Вот такая вот

play00:33

непонятная ерунда потому что у нас

play00:34

потоки не были между собой

play00:35

синхронизированы и так как сейчас у нас

play00:37

ресурс который используется для вывода

play00:38

данных это консоль и два потока работа с

play00:41

ней одновременно то есть разделяет её и

play00:43

не синхронизированы между собой мы

play00:44

получили вот такую вот ерунду на экран

play00:46

Хотя должны были получить два разных

play00:47

прямоугольника во всяком случае когда мы

play00:48

запускали эти функции эту функцию вот на

play00:50

потоке так и происходило так вот для

play00:52

того чтобы синхронизировать работу вот

play00:53

этих двух потоков чтобы решить проблему

play00:55

совместного использования Ну в данном

play00:56

случае консоли Но это может быть к

play00:57

примеру двусвязный список какие-то

play00:59

другие сложные ресурсы которы нужно

play01:00

модифицировать мы должны использовать

play01:01

текс мы уже с вами подключили мтек с

play01:03

помощью Илю и собственно вот здесь у

play01:04

меня закомментировать мтек ну по сути

play01:06

это объект класса МК Называется он MTX

play01:08

как он используется нам с помощью Текса

play01:10

нужно указать ту секцию в нашем коде

play01:13

которая должна работать только с одним

play01:15

потоком чтобы текс её защищал в случае

play01:17

если с ней уже работает один из потоков

play01:19

и к ней попытается обратиться другой

play01:20

поток текс другому потоку говорит нет

play01:22

подожди сейчас этот участок кода уже

play01:23

выполняется другим потоком А в тот

play01:24

момент когда этот участок Куда

play01:26

выполнился к разрешает другому потоку

play01:28

который пытается получить доступ к этому

play01:29

пому участку кода также работаеть с этим

play01:31

участком как вообще всё это

play01:32

реализовывается у объекта типа MX в

play01:35

нашем случае MTX есть два метода loock и

play01:39

Unlock Ну там где loock - это понятное

play01:42

дело что доступ закрыт а там где

play01:45

Unlock означает что участок кода

play01:47

выполнился и теперь другой поток может

play01:49

получать доступ к этому участку кода

play01:51

Таким образом мы с помощью вот этого

play01:53

мьютекс написали что вот отсюда до сюда

play01:56

вот этот участок кода у нас одновременно

play01:58

может использоваться только одним

play01:59

потоком

play02:00

обращаю Ваше внимание на то что

play02:01

обязательно нужно вызвать мело потому

play02:03

что должен дать возможность остальным

play02:05

потокам знать о том что всё-таки уже

play02:08

этот участок кода разблокирован его

play02:10

можно использовать То есть к илок есть

play02:12

ещ другие моменты с ми мы рассмотрим и

play02:14

далее в других уроках но пока вот для

play02:15

базового пониманием ютек это достаточно

play02:17

запустим наш код сейчас и посмотрим что

play02:18

у нас

play02:20

получится Как видите у нас выявилось два

play02:22

отдельных прямоугольника мы

play02:25

синхронизирован случае нашу консоль от

play02:28

совместного использования то таким образ

play02:30

мы моли бы использовать двусвязный

play02:31

список если бы один поток например свал

play02:33

оттуда элементы другой добавлял или

play02:34

удалял Ну то есть принцип тот же самый

play02:36

только здесь вместо вот этого участка

play02:37

кода у нас должен быть тот участок кода

play02:39

который мы хотим защитить при совместном

play02:41

использовании Казалось бы всё так просто

play02:42

всё прекрасно работает но осталось

play02:44

обратить внимание ещё на один важный

play02:46

момент Давайте засе время выполнения

play02:48

программ вот здесь прямо перед созданием

play02:49

потоков создадим объект сий нам

play02:53

достаточно его лишь создать для того

play02:55

чтобы он измерил время выполнения нашей

play02:56

программы о том как он работает уже

play02:57

говорил у нас был отдельный урок

play02:58

запустим нашу программу смотрим сколько

play03:00

будет выполняться наш код мы получили 2

play03:02

секунды Давайте ещ раз запустим конечно

play03:05

там есть небольшая погрешность но она

play03:06

для нас не столь важна Суть в том что мы

play03:08

получили 2 секунды Теперь давайте

play03:16

уберём синхронизацию потоков с помощью

play03:19

Текса UN и посмотрим сколько теперь

play03:21

будет выполняться наш код наш код

play03:23

выполнялся секун Я думаю вы можете

play03:26

догадаться в ЧМ подвох синхронизации

play03:27

потоков Мони это узкое место в скорости

play03:30

выполнения вашей программы потому что к

play03:32

вот этому коду может получить доступ

play03:34

только один поток но в этом же и суть

play03:36

синхронизации и защиты ресурсов Это

play03:38

значит что остальные потоки в это время

play03:40

ничего не будут делать таким образом

play03:41

сейчас у нас было два потока и если они

play03:43

работают одновременно они работает одну

play03:46

секунду Если мы с помощью мьютекс

play03:48

защищаем вот эту логику синхронизирует

play03:51

потоков мы получаем время работы в два

play03:53

раза больше как будто мы вообще не

play03:54

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

play03:55

кому-то из вас может прийти в голову

play03:56

мысль А нафига вообще нужна тогда

play03:58

многопоточность Ну если вот такая

play03:59

ситуации е фактически нет Суть в том что

play04:01

мы же не используем ютекс везде на всём

play04:03

коде подряд а только на тех критически

play04:05

важных секциях в которых должна быть

play04:06

синхрони работа между потоками и на тех

play04:08

ресурсах которые дож быть защищены при

play04:10

совместном использовании что это

play04:11

означает это означает что до вот этой

play04:13

логики или после неё в нашей функции

play04:15

котору мы передали в отдельный поток

play04:16

могут выполняться ещ какие-то действия и

play04:18

они тоже могут занимать длительный

play04:19

период времени Нони к примеру не требуют

play04:21

синхронизации так вот они у нас

play04:22

выполнятся в много поточно режиме Вот

play04:23

именно момент синхронизации Он у нас в

play04:25

программе будет таким узким местом по

play04:26

сути бутылочным горлышком в

play04:28

производительности нашей программы

play04:29

давайте рассмотрим конкретный пример

play04:31

допустим представим что у нас есть

play04:32

какой-то момент долго исполняемого кода

play04:34

до нашей критической секции и после

play04:36

нашей критической секции для того чтобы

play04:38

эмулировать это долго исполняемый код я

play04:39

просто буду приставить поток то есть

play04:41

слить его на 2 секунды до критической

play04:42

секции и после критической секции для

play04:44

частоты эксперимента запустим всё это

play04:46

дело в одно потоке и посмотрим Сколько

play04:47

времени оно будет выполняться так вот у

play04:49

нас один Принт а вот второй Принт

play04:50

запускаем и

play04:57

смотрим Итого наша программа

play05:00

секунд теперь запустим ВС это дело в

play05:02

двух потоках Обратите внимание Так как у

play05:05

нас вот этот момент который долго

play05:06

выполняется и вот этот момент находится

play05:08

не в критической секции то есть не

play05:09

защищённом ксом не синхронизируется Это

play05:11

означает что вот эти вот моменты могут

play05:13

выполняться одновременно в разных

play05:15

потоках Это значит что мы получим

play05:17

выигрыш производительности запускаем и

play05:18

смотрим Сколько времени всё это дело

play05:23

займёт наш код выполнился за 6 секунд а

play05:26

теперь давайте уберём защиту критической

play05:28

секции пусть теперь всё колба

play05:32

подряд 5 секунд То есть как видите на

play05:35

нашу критическую секцию уходит 1 секунда

play05:36

Ну я по грубым прикидкам говорю но я

play05:38

думаю вы понимаете когда мы использовали

play05:40

всё это в одно потоке наш код вообще в

play05:42

принципе 10 секунд выполнялся таким

play05:44

образом Хотя текс и является по сути

play05:48

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

play05:49

нашей системы потому что тическая секция

play05:51

приостанавливает многопоточность Ну по

play05:53

сути это так и есть всё равно мы

play05:54

получаем выигрыш потому что другие

play05:56

задачи на выполнение которых может

play05:57

требоваться длительный период времени и

play05:58

которые не требуют низации которые

play06:00

например откуда-то грузят данные просто

play06:01

долго они могут всё равно выполняться в

play06:03

нескольких потоках и Обратите внимание

play06:05

если мы к примеру модифицируем код ещё

play06:07

вот так вот добавим ещё один поток

play06:09

Пускай здесь будет Вот такая вот штука и

play06:12

задним ещё Т3 посмотрим сколько будет

play06:14

выполняться наш

play06:18

код 7 секунд а если мы выполним всё это

play06:21

в одном потоке то 15 секунд Мы точно

play06:23

Будем ждать

play06:32

так вот в одно потоке 15 секунд

play06:34

многопоточности 7 секунд таким образом

play06:36

ещё раз обращаю Ваше внимание что хотя

play06:38

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

play06:39

работать с теми ресурсами которые Он

play06:41

защищает для которых нужна синхронизация

play06:43

и с которыми мы не можем работать

play06:44

одновременно во всех потоках всё равно

play06:45

мы получаем выигрыш многопоточности но

play06:46

только в тех задачах которые не требуют

play06:48

такой вот защиты и синхронизации Ну и в

play06:49

случае когда мы работаем с несколькими

play06:51

потоками и с

play06:52

разделяемых ресурсах выполняется некая

play06:54

последовательность операций которая

play06:56

является по сути некой транзакцией то

play06:57

есть должна выполниться вся подряд

play06:59

именно для того чтобы операция считалась

play07:01

завершённое нам нужно использовать к и

play07:02

собственно на этом У меня пока всё если

play07:04

вам понравился этот урок он был для вас

play07:05

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

play07:06

поставить лайк

play07:09

под Приветствую вас друзья Меня зовут

play07:11

Сергей мы с вами продолжаем изучать

play07:12

библиотеку стандартных шаблонов и сталь

play07:14

в языке программирования c+ Plus данный

play07:15

урок является прямым продолжением

play07:17

прошлого урока где мы говорили о том как

play07:18

работает алгоритм equal и алгоритм Miss

play07:20

Match Поэтому если вы его не видели то

play07:22

рекомендую начать просмотр именно с него

play07:23

ссылка внизу в описании и собственно в

play07:25

том уроке Я обещал что расскажу Каким

play07:26

образом мы можем использовать вместе с

play07:28

алгоритмом equal бинарный предикат

play07:30

собственно Вот именно этим мы сегодня и

play07:32

займёмся Не забудьте поставить лайк под

play07:33

этим видео если вам интересны уроки о

play07:35

библиотеке стандартных шаблонов stl Ну и

play07:37

собственно продолжим теперь рассмотрим

play07:39

Как использовать алгоритм equal с

play07:40

предикатом Обратите внимание я писал

play07:43

простой clp который отображает две точки

play07:45

в пространстве пос X и Y Ну как геометр

play07:47

я думаю вы помните этот пример Я

play07:48

показывал его уже в прошлых уроках здесь

play07:49

у нас есть конструктор который принимает

play07:51

два эти поля X и Y и инициализирует

play07:53

собственно эти поля в конкретном

play07:54

экземпляре класса У нас есть два вектора

play07:56

типа Point которые я так и оставил с

play07:58

названием Error и Error но теперь они

play08:00

содержат по три объекта класса Point в

play08:02

данном случае у нас эти три объекта и в

play08:03

том и в том векторе идентичны Обратите

play08:05

внимание сейчас если я собираю проект мы

play08:07

получаем ошибку при компиляции Проблема

play08:08

в том что я специально не

play08:11

переопределить если бы он был

play08:13

переопределить И вот такую вот обычную

play08:16

логику алгоритма equal этот алгоритм

play08:18

просто бы использовал этот оператор в

play08:19

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

play08:20

использовать с алгоритмом equal какой-то

play08:22

В класс в котором такого оператора

play08:23

сравнения нету либо же хотите сравнивать

play08:25

этот класс по какой-то именно своей

play08:27

логике которая реализована не так как в

play08:28

операторе сравнения вам нужно

play08:29

использовать предикат собственно что мы

play08:31

сейчас и будем делать если вы не помните

play08:33

что такое предикаты и анонимные функции

play08:34

то нужные ссылки найдёте внизу в

play08:35

описании под этим видео А мы продолжим

play08:37

Итак следующим параметром после того как

play08:38

мы указали начало конец первой коллекции

play08:40

начало конец Второй коллекции у нас идёт

play08:41

тот самый предикат который позволит нам

play08:42

сравнить каким-то образом эти две

play08:44

коллекции так как захотим мы это у нас

play08:45

бинарный предикат который должен

play08:47

принимать два параметра и собственно это

play08:48

у нас будет два объекта класса

play08:52

Point P1 и P2 принимаем эти параметры по

play08:55

ссылке потому что нам не нужно будет

play08:56

копировать весь объект А мы можем просто

play08:57

обратиться по адресу в памяти чтобы

play08:58

посмотреть что Что там находится а

play09:00

константные ссылки - Это для того чтобы

play09:01

мы сами себя

play09:03

обезопасит выполнения алгоритма equal и

play09:05

обработки нашей логики и собственно наш

play09:07

бинарный предикат должен возвращать

play09:09

какое-либо логическое булевое значение

play09:11

определим здесь переменную в которой

play09:12

будем хранить этот результат и её мы

play09:14

будем возвращать в эту переменную мы

play09:16

должны присваивать результат сравнения

play09:18

наших двух входящих объектов и в данном

play09:21

случае мы можем сами выбирать как мы

play09:23

будем сравнивать ну к примеру мы можем

play09:24

реализовать именно ту логику которая

play09:26

ожидается от нас в перегружено операторе

play09:30

то есть здесь мы проверяем на равенство

play09:31

два поля что поля X между объектом P и

play09:34

P2 равны между собой и поля Y тоже между

play09:35

ними равны Таким образом мы сравниваем

play09:37

две точки конкретно по двум полям и в

play09:39

случае если эти объекты равны У нас вот

play09:41

здесь в БМ результате будет Т И мы

play09:43

вернём этот результат на самом деле эта

play09:44

переменная нам здесь не нужна мы можем

play09:45

возвращать сразу результат сравнения

play09:47

этих полей так как нам хочется в данном

play09:49

случае если мы сейчас запустим нашу

play09:50

программку мы получим единичку потому

play09:53

что у нас объекты класса Point которые

play09:55

являются элементами двух коллекций

play09:57

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

play09:59

идентичные значение полей если мы к

play10:01

примеру один поит например в первом

play10:03

векторе смести в другом порядке мы

play10:04

получим уже результат fse но это как тот

play10:06

же самый пример целыми числами если у

play10:08

нас две последовательности содержат

play10:09

одинаковые элементы Но они в разных

play10:10

индексах то алгоритм и считает что это

play10:13

разные последовательности и нам

play10:14

предварительно необходимо такие

play10:15

последовательности отсортировать вернём

play10:17

как было Сейчас если мы запускаем у нас

play10:19

опять-таки две эти последовательности

play10:21

равны мы получаем единицу А если мы к

play10:24

примеру изменим значение какого-то Из

play10:26

полей вот к примеру в первом поинте в

play10:28

первом поит первой коллекции поменяем

play10:30

значение новой с поля мы уже опять-таки

play10:31

получим значение ноль собственно для

play10:32

того чтобы понять действительно Как это

play10:33

работает необходимо посмотреть в

play10:34

отладчике Ну это в принципе такой вот

play10:36

закон если хотите хорошо разбираться что

play10:37

как работать смотрите в отладчике или же

play10:39

если хотите решить какую-то проблему

play10:40

Итак у нас имеется две коллекции рр

play10:42

которая содержит три точки вот они есть

play10:45

коллекция ror2 которая также содержит

play10:47

три точки Вот она две разные коллекции

play10:50

содержат абсолютно идентичные объекты

play10:52

класса и в одном и том же порядке

play10:53

поэтому алгоритм equal рассмотрит их как

play10:55

идентично у нас точка установлена

play10:56

девяносто пятой строчке кода и Сейчас мы

play10:57

посмотрим как будет обрабатывать

play10:58

алгоритм и эти объекты Итак у нас первая

play11:02

итерация по сути мы проверяем вот эти

play11:03

вот два объекта проверяет у нас эти

play11:04

объекты предикат которые мы написали он

play11:06

принял эти два первых объекта

play11:07

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

play11:09

объекта p1x равен 1 у объекта p2x раве 1

play11:12

то есть по полям X они уже равны и мы

play11:14

проверяем опять-таки эти объекты по

play11:15

полям Y у первого объекта - это три и у

play11:17

второго три То есть соответственно вот

play11:19

эти два объекта у нас уже равны делаем

play11:21

ещё шаг у нас вторая итерация сейчас мы

play11:23

проверяем вот эти объекты то есть по

play11:24

сути алгоритм просто перебирает две

play11:25

последовательности и сравнивает их

play11:27

объекты вот сейчас это уже вторая то с

play11:29

им 4 и иреком пятёркой второй объект у

play11:32

нас идентичный сравнивает их поля и

play11:33

соответственно алгоритм и на основе

play11:35

этого сравнения выносит решение равны

play11:36

объекты или нет X4 X4 у двух разных

play11:39

объектов P1 P2 Y тоже идентичный Ну и

play11:41

третья операция - это вот эти вот уже

play11:42

объекты Ну я думаю вы поняли что раз мы

play11:44

сравниваем по всем полям объекты которые

play11:46

у нас идентичны в обоих коллекциях то

play11:48

соответственно наш алгоритм Ил вернул

play11:50

значение тру если мы изменим порядок

play11:51

этих объектов или просто по одному и

play11:53

тому же индексу будут объекты с

play11:55

различными полями вот к пример мы здесь

play11:56

двойку уже кнм и запустим опять-таки наш

play11:58

алгоритм смотрите что у нас получится мы

play11:59

уже на первую итерацию гда будем

play12:01

сравнивать вот эти вот два объекта

play12:03

получим их вот сюда в параметры P1 у нас

play12:05

X = 1 y = 2 а P2 Y = 1 Y = 3 то есть вот

play12:09

эта вот логика которая будет сравнивать

play12:10

у нас по полям уже вернёт флс здесь X1 и

play12:12

здесь X1 А здесь y = 2 а здесь Y = 3 то

play12:15

есть это разные объекты вот эта вот

play12:16

логика вернёт fse соответственно

play12:17

алгоритм equal уже поймёт что эти

play12:19

контейнеры не одинаковы и как результат

play12:20

мы получим fse Ну вот таким вот образом

play12:22

собственно работает алгоритм equal с

play12:24

бинарным предикатом и на этом У меня на

play12:26

сегодня всё Если вам понравилось это

play12:27

видео оно было для вас полезным

play12:28

интересным не забудьте поставить под ним

play12:29

лайк от этого зависит

play12:39

раз вас друзья Меня зовут Сергей мы с

play12:41

вами продолжаем изучение многопоточного

play12:43

программирования в языке c+ Plus

play12:44

сегодняшний урок У нас будет прямым

play12:45

продолжением прошлого урока где мы

play12:47

говорили о мьютекс о защите разделяемых

play12:48

данных и синхронизации потоков и сегодня

play12:50

мы будем говорить о такой конструкции

play12:51

как Lock Guard для чего это вообще нужно

play12:53

как это использовать Друзья если вам

play12:54

интересна тема многопоточного

play12:55

программирования то не забудьте

play12:56

поддержать это видео лайком Ну и

play12:58

собственно приступим к Теме нашего урока

play12:59

Итак Lock Guard - это класс задачей

play13:01

которого является захватить ютекс в

play13:03

конструкторе при создании объекта такого

play13:04

класса и освободить этот текс в

play13:05

деструктор в тот момент когда объект

play13:07

этого класса будет покидать какую-либо

play13:09

область видимости То бишь по сути в

play13:10

момент создания объекта класса lck Guard

play13:12

он вызывает вот эту штуку а в тот момент

play13:14

когда этот объект уничтожается в

play13:15

деструктор вызывается вот эта штука для

play13:17

чего это нужно и как это используется в

play13:19

прошлом уроке мы с вами выяснили что

play13:20

ютекс сам по себе Нам нужен для того

play13:22

чтобы синхронизировать потоки и защитить

play13:24

какой-то разделяемый участок кода между

play13:26

несколькими потоками для того чтобы

play13:28

какая-то определённая последовательность

play13:29

действий выполнила с транзакций и у нас

play13:31

даже был пример с распечатывания

play13:33

геометрических фигур в консоли с помощью

play13:35

разных потоков в данном случае у нас

play13:36

здесь консоль выступает разделяемый

play13:37

ресурсом которые делится между

play13:38

несколькими потоками так вот если мы вот

play13:40

этот момент работы с консолью защищаем с

play13:42

помощью лока мы получаем корректную

play13:44

работу нашей программы то есть в данном

play13:45

случае у нас три потока отрисовывать Т

play13:47

геометрические фигуры всё отработала

play13:48

корректно если мы Лок убираем то есть не

play13:51

защищаем эту критически важную секцию с

play13:52

разделяемый ресурсами получаем вот такую

play13:54

вот непонятную ерунду то есть

play13:55

целостность данных у нас нарушена так

play13:56

вот важной особенностью при работе с

play13:58

мьютекс является что нам нужно указать

play14:00

конкретно вот секцию которую мы защищаем

play14:02

и в которой будет поток

play14:03

синхронизироваться с помощью методов L и

play14:04

Unlock в тот момент когда один из

play14:05

потоков добирается до вот этой

play14:07

конструкции вызывается метод Лок то

play14:09

другие потоки добравшись до этой же

play14:10

конструкции дальше пойти не могут аж до

play14:12

того момента пока первый поток который

play14:14

захватил тек не выполнит весь этот код

play14:16

не доберётся до вот этой его строчки

play14:17

кода и не освободит к и только после

play14:19

этого какой-то другой поток сможет

play14:20

захватить ВК и также выполнить этот код

play14:21

Ну и собственно так между всеми потоками

play14:23

так вот что будет если мы к примеру

play14:25

когда будем писать наш код случайно

play14:26

забудем вот этот момент по сути что мы

play14:28

сдела мы сказали что поток который

play14:30

выполняет функцию Print захватывает к

play14:32

выполняет код но никогда не освобождает

play14:34

этот к это будет означать что остальные

play14:36

потоки так и будут не скончания времён

play14:37

ожидать покажем текс освободится Как

play14:39

видите у нас есть три потока каждый

play14:42

поток рисует свою фигуру но отрисовать

play14:43

только одна фигура Потому что первый

play14:44

поток который успел захватить в к

play14:46

выполнил этот код но КС так и не

play14:48

освободил также ничего хорошего не

play14:49

выйдет в том случае если мы забудем к

play14:51

примеру вот эту часть кода то есть мы не

play14:53

заблокирую текс Ну во-первых все потоки

play14:56

сразу же получат доступ к тому участку

play14:57

хода который мы хотели по идее защитить

play14:59

и во-вторых каждый из этих мкв каждый из

play15:01

этих потоков попытается выполнить метод

play15:03

Ало Текса то есть разблокировать его

play15:05

хотя он до этого не был залоченный

play15:06

закрытый и соответственно благодаря

play15:07

этому мы тоже получим ошибку так вот

play15:09

собственно для того чтобы вот такой вот

play15:10

проблемы избегать случайной вот такой

play15:12

вот ошибки нам и нужен Guard таким

play15:14

образом между вызова вот этих двух

play15:15

методов Unlock мы можем использовать тот

play15:18

самый Давайте создадим объект такого

play15:19

класса мы должны указать ему тип данных

play15:21

с которым он будет работать это и далее

play15:23

при создании объекта этого Гарда мы

play15:25

должны вот тот самый к который мы

play15:26

создавали для синхронизации наших

play15:28

потоков передать ему рут таким образом

play15:29

вот этот loog Guard выполнит сам за нас

play15:32

вот те операции Log и Unlock Давайте

play15:34

посмотрим как это всё произойдёт а затем

play15:35

более подробно поговорим То есть как

play15:37

видите у нас сейчас все наши потоки

play15:38

синхронизированы именно в области нашей

play15:40

функции Print я уже говорил что задача

play15:42

Log Guard захватить к в конструкторе

play15:44

освободить его в деструкторы так как

play15:45

захват мтек сов происходит в

play15:46

конструкторе а конструктора

play15:47

соответственно у нас вызывается при

play15:48

создании объекта то вот в этот момент

play15:50

когда вы создаёте объект типа lard

play15:52

считайте что вы написали MX Log вторым

play15:54

важным моментом является тогда когда у

play15:56

этого объекта ard вызове деструктор в

play15:57

данном случае дестру У нашего объекта

play15:59

Guard который является типом Guard

play16:01

вызове в момент тогда когда вот этот

play16:03

объект будет выходить из области

play16:04

видимости этой функции Print таким

play16:06

образом вот э вот наша вся функция Print

play16:08

благодаря тому что в ней просто в момент

play16:10

её вызова найперше операции которая

play16:12

происходит является создание объекта

play16:14

типа lard то этой функция и является

play16:16

защищённой с помощью тек то есть

play16:18

различные потоки могут получать к ней

play16:20

доступ лишь по очереди не все

play16:21

одновременно если мы вот сейчас за

play16:22

комментируем этот момент созданием

play16:23

объекта типа lard то мы получим эту

play16:25

ситуацию Когда у нас была без

play16:26

синхронизации если у нас функция должна

play16:29

быть полностью синхронизирована То есть

play16:31

все действия которые выполняются в ней

play16:32

должны быть только через utex все

play16:34

операции должны быть защищены то

play16:35

использование Log для нас идеальный

play16:37

вариант потому что мы точно не забудем

play16:38

залочить ютекс и затем не забудем его

play16:40

освободить потому что за нас это сделает

play16:42

вот этот объект lard прямо в своём

play16:43

конструкторе и в деструкторы но я думаю

play16:45

вы помните что тот код который

play16:46

используется вместе с мтек то есть

play16:48

защищённая область которая используется

play16:50

для синхронизации потоков она по сути

play16:51

полностью выполняется в одно поточно

play16:53

режиме потому что другие потоки по

play16:54

понятным причинам потому что для этого

play16:56

используется мтек не могут работать с

play16:57

этими же данными в тот же самый момент а

play16:59

так вот если мы хотим в этой же функции

play17:01

использовать какие-то моменты которые

play17:03

должны работать в многопоточного режиме

play17:05

тогда нам уже с лордом работать не

play17:07

настолько удобно но тем не менее это

play17:09

тоже решаемо И как вы помните у нас был

play17:11

ещё вот такой пример что у нас в начале

play17:13

вызова функции Print и в конце вызова

play17:15

функции Print слипался наш поток то есть

play17:17

приостанавливается на 2 секунды для того

play17:19

чтобы эмулировать какую-то трудную и

play17:20

долгую задачу В этом же методе но

play17:22

которое не требует синхронизации То есть

play17:24

то что можно было не лочи с помощью

play17:26

Текса но в случае с мтек сом мы вот вот

play17:28

этим Так мы просто берём и указываем что

play17:30

я вот хочу вот эту область залочить

play17:32

здесь освободить и по сути вот этот код

play17:34

у нас будет выполняться синхронно А вот

play17:36

этот вот эти два слипа асинхронно Ну то

play17:38

есть там как минимум на каждую функцию

play17:39

Print будет 4 секунды она должна

play17:41

выполниться три раза если в одно поточно

play17:43

режиме то это уже 12 секунд плюс ещё вот

play17:45

эта операция Но если вы многопоточности

play17:47

то вот этот момент и вот этот момент

play17:48

может выполняться одновременно во всех

play17:50

потоках и только вот этот момент

play17:52

маленький участок кода выполняется

play17:53

синхронно Таким образом мы всё равно в

play17:54

производительности выигрываем Но я это

play17:56

всё показывал в прошлом уроке поэтому

play17:57

рекомендовал вам его посмотреть таким

play17:59

образом смотрите общее время выполнения

play18:00

программы 7 секунд Хотя если посчитать

play18:02

однопоточный режим Да сколько

play18:03

выполняется функция Print опять-таки это

play18:05

ну 12 секунд плюс ещё вот этот код Ну то

play18:07

есть там как минимум 15 секунд будет мы

play18:08

в прошлый раз измеряли так вот к чему

play18:10

это всё рассказывал к тому что если мы

play18:11

используем Unlock мы легко можем указать

play18:13

вот просто конкретную область которую

play18:14

нам нужно синхронизировать в случае если

play18:15

мы работаем с лордом Ну да допустим там

play18:18

где нам нужен ло мы просто создаём

play18:19

объект Guard Как нам указать ему Где нам

play18:22

нужно чтобы он уничтожил И тем самым

play18:24

вызвал свой

play18:25

деструктор там где нам нужно разлочить

play18:27

тот код там где нужно его освободить

play18:28

потому что Вот в такой реализации

play18:29

получается что вот эта вот часть кода у

play18:31

нас будет выполняться в нескольких

play18:32

потоках потому что она не защищена мтек

play18:34

сам лордом а вот всё что ниже будет уже

play18:36

работать в синхронном режиме и даже вот

play18:38

эта вот часть которая по идее у нас

play18:39

могла бы выполняться в разных потоках

play18:40

одновременно потому что для неё

play18:41

синхронизация не нужна Почему так да

play18:43

потому что объект Guard будет уничтожен

play18:44

только вот здесь когда он выплатит за

play18:46

область видимости функции Print и у него

play18:47

будет вызван деструктор что мы можем

play18:49

сделать вот в данный момент Ну во-первых

play18:51

давайте действительно проверим что у нас

play18:52

получится по скорости выполнения Итак

play18:54

помните Когда я делал только с помощью

play18:56

Log вот эту область кода с д дей по Т

play18:58

восьмой кода у нас только что было 7

play18:59

секунд если не верите можете назад

play19:00

открутить а сейчас мы запустим с

play19:09

Гарм получается 11 Секунд в чём проблема

play19:12

Ну проблема в том что хотя вот эту вот

play19:13

часть кода мы и можем выполнять

play19:14

асинхронно то есть одновременно Но вот э

play19:16

вот часть кода которое мы могли бы

play19:17

выполнять в разных потоках одновременно

play19:18

у нас всё равно синхронизируется потому

play19:20

что ard объект ard её захватывает свой

play19:22

контекст и уничтожается он только вот

play19:23

здесь а значит укса вызывается уже после

play19:26

вот этого момента что мы можем с этим

play19:28

сделать мы можем просто ограничить

play19:29

область видимости для нашего объекта р с

play19:30

помощью скобочек

play19:32

вот таким вот образом Обратите внимание

play19:35

если мы за вот этими вот скобочка

play19:37

попытаемся написать название объекта р

play19:39

то нам Седа разработки будет ругаться

play19:41

что она не видит такого объекта потому

play19:42

что мы ограничили область видимости вот

play19:44

этого вот всего кода вот этими скобочка

play19:46

это в принципе ещё самые самые азы мы

play19:47

где-то их изучали там ещё в начале

play19:48

изучения C плюсов но тем не менее Вот

play19:51

такую штуку здесь можно применить таким

play19:52

образом раз область видимости объекта

play19:54

гар и вот этого кода ограничена вот

play19:55

этими вот скобочка то по сути объект гар

play19:58

будет уничтожен вот здесь теперь А вот

play19:59

эта часть кода сможет выполняться

play20:00

асинхронно то есть одновременно в

play20:02

нескольких потоках Таким образом мы

play20:03

опять-таки должны получить скорость

play20:04

выполнения около 7 секунд Давайте

play20:06

забыться и проверим Ну так как будто бы

play20:08

мы использовали обычный Лок без Лок

play20:10

Гарда Ну вот Сем и одна То есть

play20:13

практически тот же самый результат но

play20:14

только мы не беспокоились об локе и

play20:16

Unlock мтек просто указали ему область

play20:18

вот этими вот скобочка где он должен

play20:19

уничтожить то есть область его видимости

play20:22

с помощью вот этих скобочек там где он

play20:23

имеет право что-либо делать но если

play20:25

точнее мы ему просто сказали что вот вот

play20:26

здесь он уничтожит но здесь мы его

play20:28

ручками создаём В общем вот такая вот

play20:29

штука lard используется для тех же целей

play20:31

что имтек только немножечко по-другому и

play20:33

может обречь Вас от лишних ошибок вообще

play20:34

идеальная вещь если у Вас должен быть

play20:36

какой-то метод или какая-то функция

play20:37

полностью синхронизированная вы просто

play20:38

его создаёте этот гар в начале

play20:40

исполнения метода или функции и

play20:42

забываете про это всё то есть про Log

play20:43

про Unlock Ну а если же вам в этом

play20:44

методе или функции всё-таки нужно как-то

play20:46

разграничивать синхронный асинхронный

play20:48

код Ну тогда приходится использовать

play20:49

какие-то вот такие штуки и на этом У

play20:50

меня собственно Всё если вам понравился

play20:52

этот урок он был для вас полезно

Rate This

5.0 / 5 (0 votes)

Related Tags
многопоточностьсинхронизациямьютексылокгардыC++разделяемые ресурсыобъекты классаалгоритм equalбинарный предикатстлсергейпрограммирование
Do you need a summary in English?