it-notepad.com

Глава 4. Многопоточное программирование с Grand Central Dispatch

Оглавление

Введение

Понимание потоков

Использование GDC – Grand Central Dispatch

Применение групп отправки

Заключение

Введение

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

В прошлом задачи и программы были простым, и процессорам хватало сполна производительности для для обработки каждой задачи последовательно, не важно как много этих задач было. Неспроста ведь на заре развития высоких технологий Билл Гейтс утверждал что 640 кб ОЗУ будет достаточно для выполнения любых задач. Постепенно программное обеспечение становилось все сложнее и сложнее, задачи тоже становились более требовательными к вычислительным ресурсам и процессоры уже не справлялись с последовательным выполнением сложных задач. Увеличение частоты процессоров, как и увеличение кешей 3-х уровней тоже не смогли решить задачу – есть физический предел частоты процессора. Поэтому довольно быстро в серверах внедрили многопроцессорные системы, а в пользовательских компьютерах внедрили мультиядерные процессоры. В мультиядерных системах различные процессы выполняются в разных ядрах, словно на разных физических процессорах. Так сейчас в топовых процессорах Intel Raptor Lake максимальное количество ядер равно 24.

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

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

Каждый язык программирования или даже SDK предлагает свой рецепт грамотного написание параллельного кода. Для решения проблемы управления параллельного кода языка Swift, компания Apple разработала решение, называемое Grand Central Dispatch(GCD – Главная Централизованная Отправка). GCD предоставляет поддержку для параллельного кода, выполняющегося на мультипоточных платформах в iOs или macOS. Вместо того, чтобы отвлекать разработчиков на изучение, запоминание и понимания касательно деталей работы параллельного кода в потоках, Grand Central Dispatch предоставляет информацию разработчикам о коде, который в данный момент РАБОТАЕТ в параллельном режиме, и не отвлекает программистов на реализацию КАК ЭТО СДЕЛАТЬ.

Раньше приложения и программы были по большей части автономны. И сейчас речь идет скорее о периоде конца 80-ых, начало – середина 90-ых годов. Сейчас приложения зависят от многих непредсказуемых факторов, например ожидание скачивания файла или установки интернет соединения с удаленным сервером или клиентом. Во время ожидания долгой выполняющейся задачи, приложение терпеливо ожидает завершения этой задачи, словно клиент в супермаркете с одной работающей кассой. Если задача выполняется больше 5 секунд, пользователю может показаться что приложение зависло или не отвечает на его действия.

Именно поэтому при разработке приложений для iOs, использование Grand Central Dispatch предоставляет простой путь к использованию многопоточности в приложении. Потому что если один поток завис на продолжительное время в ожидании завершения задачи, другие потоки, в частности поток отвечающий за работу UI, продолжают работать как ни в чем не бывало. Используя Главную Централизованную Отправку, Ваше приложение не покажется медленным или зависшим Вашему пользователю.

Обратите внимание, что GCD работает одинаково в iOs и в macOS.

Понимание потоков

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

Создадим новое приложение, в котором умышленно заблокируем пользовательский интерфейс. Для демонстрации функционала по блокировке UI необходимо будет проделать 10 пунктов:

  1. Создадим новый iOs App проект. Убедитесь, что используем SwiftUI.
  2. После создания нажмем на ContentView файл а панели Навигатор. В панели редактора отобразится наш исходный код
  3. Добавим 3 State переменные под struct ContentView, как указано ниже:
struct ContentView: View {
    @State var message = ""
    @State var results = ""
    @State var sliderValue = 0.0

4. Создадим VStack блок ниже var body: some View и добавим следующие элементы UI: Button, TextEditor, Slider, Text:

var body: some View {
     VStack {
        Button("Click me"){
                
        }
        TextEditor(text: $results)
        Slider(value: $sliderValue)
        Text("Message = \(message)")
    }
}

Этот блок кода создает кнопку Button в верхней части экрана, Text Editor  в его середине, и элемент Slider в нижней части экрана.

5. Добавим 4 функции ниже основного блока, касающегося ContentView:

 

func fetchSomethingFromServer() -> String {
    Thread.sleep(forTimeInterval: 1)
    return "Hi there"
}

func processData(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 2)
    return data.uppercased()
}

func calculateFirstResult(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 3)
    let message = "Number of chars: \(String(data).count)"
    return message
}

func calculateSecondResult(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 4)
    return data.replacingOccurrences(of: "E", with: "e")
}

6. Изменим код кнопки Button:

Button("Click me"){
    let startTime = NSDate()
    let fetchedData = fetchSomethingFromServer()
    let processedData = processData(fetchedData)
    let firstResult =  calculateFirstResult(processedData)
    let secondResult = calculateSecondResult(processedData)
    let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
    results = resultsSummary
    let endTime = NSDate()
    message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"
}

И мы получили следующий исходный код:

import SwiftUI

struct ContentView: View {
    @State var message = ""
    @State var results = ""
    @State var sliderValue = 0.0
    var body: some View {
        VStack {
            Button("Click me"){
                let startTime = NSDate()
                let fetchedData = fetchSomethingFromServer()
                let processedData = processData(fetchedData)
                let firstResult =  calculateFirstResult(processedData)
                let secondResult = calculateSecondResult(processedData)
                let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
                results = resultsSummary
                let endTime = NSDate()
                message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"
            }
            TextEditor(text: $results)
            Slider(value: $sliderValue)
            Text("Message = \(message)")
        }
    }
}

func fetchSomethingFromServer() -> String {
    Thread.sleep(forTimeInterval: 1)
    return "Hi there"
}

func processData(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 2)
    return data.uppercased()
}

func calculateFirstResult(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 3)
    let message = "Number of chars: \(String(data).count)"
    return message
}

func calculateSecondResult(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 4)
    return data.replacingOccurrences(of: "E", with: "e")
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

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

7. Нажмите на иконку предварительного просмотра на панели Canvas

8. Попробуйте подвигать слайдер влево или вправо.  На скринштоте хорошо видно, что слайдер отвечает на наши действия.

9. Теперь попробуем запустить наш написанный код. Нажмем на кнопку Click Me. Кнопка тут же потускнеет. И попробуем двигать наш слайдер. Так как наше приложение занято выполнением продолжительной операции, нам покажется что приложение зависло или не отвечает. После завершение работы кода, мы получим следующий результат:

10. Теперь после завершения задачи наше приложение снова откликается на наши действия

Этот пример показывает как процесс может “заморозить” приложение и оно не будет отвечать на действия пользователя, хотя приложение и работает исправно. Если Вы допускаете, что Ваше приложение может периодически зависать, все может закончиться тем, что Apple может удалить его из App Store.

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

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

Когда Вы пишете код, убедитесь что Ваш код потоко-безопасный. Далее по ходу знакомства разработки на языке Swift мы еще будем не раз возвращаться к этой теме. Главное правило – любой код, который взаимодействует с пользовательским интерфейсом не является потоково-безопасным. Поскольку потоки увеличивают возможности разработчиков, разработчики Swift приложений не должны напрямую использовать потоки, чтобы не создавать потоки, которые вмешиваются в работу друг друга. Именно поэтому Apple и разработало GCD для помощи в создании простых и безопасных мультипоточных приложений. Больше про потоковую безопасность Вы можете прочитать на оффициальном сайте Apple – https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

Использование GDC – Grand Central Dispatch

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

GCD предоставляет число предопределенных очередей, включая очередь исполнения UI – главного потока. Диспетчер предоставляет нам возможность создания большого количества очередей, гораздо больше, чем может понадобиться в приложении. Но не забывайте, что мы все еще ограничены ОЗУ устройства и памятью, которое может быть выделено нашему разрабатываемому приложению. Единицы исполнения кода обязательно будут запущены в том порядке, в котором мы поместили их в очередь. Однако это не означает, что они будут завершены в том же порядке, в котором были запущены. На каждую задачу может быть выделено разное количество процессорного времени и мы не в силах изменить что либо.

Для использования GCD, первое что необходимо сделать – это создать очередь, используя ключевое слово DispatchQueue:

let queue1 = DispatchQueue(label: "queue1")

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

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

queue1.sync{ () -> Void in 
    // code here
}
queue1.async{ () -> Void in 
    // code here
}

Разберем пример работы с асинхронными задачами и убедимся что они завершаются в разное и непредсказуемое время. Для этого сделаем следующее:

1. В среде XCode сделаем следующее: Choose File -> New – Playground и выбираем пункт Blank iOs playground. Дадим имя нашей песочнице – GCDPlayground.

2. Добавим следующий код:

import UIKit

let queue1 = DispatchQueue(label: "queue1")
let queue2 = DispatchQueue(label: "queue2")
let queue3 = DispatchQueue(label: "queue3")

queue1.async { () -> Void in
    print(queue1.label)
}

queue2.async { () -> Void in
    print(queue2.label)
}

queue3.async { () -> Void in
    print(queue3.label)
}
print("Program stopped")

Этот код создает 3 очереди и запускает задачу в каждой очереди. Под задачей мы понимаем работу функции print. В конце выводится надпись: “Program stopped“.

3. Запустим наш код, нажав на кнопку Run. И посмотрим внимательно на вывод:

queue1
queue2
Program stopped
queue3

4. Запустим наш код еще раз и посмотрим на вывод в консоль:

Program stopped
queue1
queue3
queue2

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

Теперь пора посмотреть, как будут выполняться синхронные задачи. Для этого изменим в коде async на sync:

import UIKit

let queue1 = DispatchQueue(label: "queue1")
let queue2 = DispatchQueue(label: "queue2")
let queue3 = DispatchQueue(label: "queue3")

queue1.sync { () -> Void in
    print(queue1.label)
}

queue2.sync { () -> Void in
    print(queue2.label)
}

queue3.sync { () -> Void in
    print(queue3.label)
}
print("Program stopped")

Запустим и посмотрим на вывод в консоль:

queue1
queue2
queue3
Program stopped

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

Теперь вернемся к нашему прошлому примеру, где мы искусственно “зависали” наше приложение. Переработаем наше приложение для того, чтобы во время выполнения наших тяжелых задач больше не происходило зависание программы. Мы воспользуемся Grand Central Dispatch для того, чтобы исправить лаг в приложении.  Рассмотрим код, который срабатывает при нажатии на кнопку:

let startTime = NSDate()
let fetchedData = fetchSomethingFromServer()
let processedData = processData(fetchedData)
let firstResult =  calculateFirstResult(processedData)
let secondResult = calculateSecondResult(processedData)
let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
results = resultsSummary
let endTime = NSDate()
message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"

Мы свернем этот код в замыкание и запустим в очереди GCD. Однако, обратим внимание на эту строку:

results = resultsSummary

Эта строка обновляет значение переменной, которая связана в нашем коде с Text Editor. Выше мы уже писали, что при многопоточном программировании необходимо всегда помнить о том, какой код является потоково – безопасным. Обновление и любой доступ к UI таковым не является. И если мы воспользуемся примером Выше, мы получим ошибку при запуске нашего приложения. Для этого мы запустим асинхронную задачу в очереди главного потока:

Button("Click me"){
    let startTime = NSDate()
    let queue = DispatchQueue.global(qos: .default)
    queue.async {
         let fetchedData = fetchSomethingFromServer()
         let processedData = processData(fetchedData)
         let firstResult =  calculateFirstResult(processedData)
         let secondResult = calculateSecondResult(processedData)
         let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
         results = resultsSummary
         let endTime = NSDate()
         message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"
    }
}

Во первых мы захватываем глобальную очередь, которая всегда доступна при использовании функции DispatchQueue.global(). Эта функция принимает один аргумент в качестве определения приоритета. Если Вы указываете разные параметры приоритета в качестве аргумента, Вы получите различные глобальные очереди, которым операционная система назначит разные уровни приоритета выполнения.  Сейчас мы придерживаемся приоритета по умолчанию.

Замыкание потом передается очереди через функцию queue.async(). Диспетчер получает замыкание и помещает в очередь на выполнение. Код будет  запущен в фоновом потоке и выполнен, так же как будто он выполнялся в главном потоке.

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

Измененный код ContentView теперь выглядит так:

import SwiftUI

struct ContentView: View {
    @State var message = ""
    @State var results = ""
    @State var sliderValue = 0.0
    var body: some View {
        VStack {
            Button("Click me"){
                let startTime = NSDate()
                let queue = DispatchQueue.global(qos: .default)
                queue.async {
                    let fetchedData = fetchSomethingFromServer()
                    let processedData = processData(fetchedData)
                    let firstResult =  calculateFirstResult(processedData)
                    let secondResult = calculateSecondResult(processedData)
                    let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
                    results = resultsSummary
                    let endTime = NSDate()
                    message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"
                }
            }
            TextEditor(text: $results)
            Slider(value: $sliderValue)
            Text("Message = \(message)")
        }
    }
}

func fetchSomethingFromServer() -> String {
    Thread.sleep(forTimeInterval: 1)
    return "Hi there"
}

func processData(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 2)
    return data.uppercased()
}

func calculateFirstResult(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 3)
    let message = "Number of chars: \(String(data).count)"
    return message
}

func calculateSecondResult(_ data: String) -> String {
    Thread.sleep(forTimeInterval: 4)
    return data.replacingOccurrences(of: "E", with: "e")
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Теперь нажмем на кнопку Live Preview на панели Canvas. Нажмем на кнопку Click Me в верхней части экрана эмулятора. Теперь попробуем переместить слайдер влево или вправо. Не забывайте, что в этот момент у нас выполняется код. После завершения его работы мы увидим результаты, отобразившиеся на экране эмулятора. Используя многопоточное программирование, мы можем спокойно взаимодействовать с UI, пока у нас выполняются различные вычисления

Применение групп отправки

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

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

Для создание группы отправки, мы должны создать объект DispatchGroup:

let group = DispatchGroup()

Затем мы запустим каждую очередь внутри группы отправки:

queue.async(group: group){
    firstResult = calculateFirstResult(processedData)
}

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

group.notify(queue: queue){
   let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
   results = resultsSummary
   let endTime = NSDate()
   message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"
}

Главное различие заключается в том, что group.notify и queue.async очереди должны иметь доступ к переменным

firstResult и secondResult. Поэтому мы должны объявить эти переменные за пределами этих 2-х очередей таким образом:

var firstResult : String!
var secondResult: String!

Для того, чтобы посмотреть как работают группы отправки, сделаем следующее:

  1. Загрузим наш проект Chapter4 в среду XCode.
  2. Откроем файл ContentView в панели Навигатора
  3. Кнопку Click Me заключим в горизонтальную UI группу:
    VStack {
         HStack{
             Button("Click me"){
  4. Добавим элемент Spacer() после нашей кнопки
  5. После элемента Spacer() добавим вторую кнопку:
    Button("Dispatch Groups"){
         let startTime = NSDate()
         let queue = DispatchQueue.global(qos: .default)
         queue.async {
            let fetchedData = fetchSomethingFromServer()
            let processedData = processData(fetchedData)
            var firstResult : String!
            var secondResult: String!
            let group = DispatchGroup()
            queue.async (group: group) {
                firstResult =  calculateFirstResult(processedData)
            }
            queue.async (group: group) {
                secondResult = calculateSecondResult(processedData)
            }
            group.notify(queue: queue){
                let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
                results = resultsSummary
                let endTime = NSDate()
                message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"
            }
        }
    }

    Исходный код измененного файла ContentView должен выглядеть следующим образом:

    //
    //  ContentView.swift
    //  Chapter4
    //
    //  Created by Timur on 05.03.2023.
    //
    
    import SwiftUI
    
    struct ContentView: View {
        @State var message = ""
        @State var results = ""
        @State var sliderValue = 0.0
        var body: some View {
            VStack {
                HStack{
                    Button("Click me"){
                        let startTime = NSDate()
                        let queue = DispatchQueue.global(qos: .default)
                        queue.async {
                            let fetchedData = fetchSomethingFromServer()
                            let processedData = processData(fetchedData)
                            let firstResult =  calculateFirstResult(processedData)
                            let secondResult = calculateSecondResult(processedData)
                            let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
                            results = resultsSummary
                            let endTime = NSDate()
                            message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"
                        }
                    }
                    Spacer()
                    Button("Dispatch Groups"){
                        let startTime = NSDate()
                        let queue = DispatchQueue.global(qos: .default)
                        queue.async {
                            let fetchedData = fetchSomethingFromServer()
                            let processedData = processData(fetchedData)
                            var firstResult : String!
                            var secondResult: String!
                            let group = DispatchGroup()
                            queue.async (group: group) {
                                firstResult =  calculateFirstResult(processedData)
                            }
                            queue.async (group: group) {
                                secondResult = calculateSecondResult(processedData)
                            }
                            group.notify(queue: queue){
                                let resultsSummary = "First: [\(firstResult)]\nSecond: [\(secondResult)]"
                                results = resultsSummary
                                let endTime = NSDate()
                                message = "Completed in \(endTime.timeIntervalSince(startTime as Date)) seconds"
                            }
                        }
                    }
                }
                TextEditor(text: $results)
                Slider(value: $sliderValue)
                Text("Message = \(message)")
            }
        }
    }
    
    func fetchSomethingFromServer() -> String {
        Thread.sleep(forTimeInterval: 1)
        return "Hi there"
    }
    
    func processData(_ data: String) -> String {
        Thread.sleep(forTimeInterval: 2)
        return data.uppercased()
    }
    
    func calculateFirstResult(_ data: String) -> String {
        Thread.sleep(forTimeInterval: 3)
        let message = "Number of chars: \(String(data).count)"
        return message
    }
    
    func calculateSecondResult(_ data: String) -> String {
        Thread.sleep(forTimeInterval: 4)
        return data.replacingOccurrences(of: "E", with: "e")
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

     

  6. Нажмем на иконку Live Preview на панели Canvas.
  7. Нажмем на кнопку Click Me. Дождемся того, как процесс вычисления закончится и посмотрим на время выполнения кода: 10.01649 секунды.
  8. Теперь нажмем на кнопку Dispatch Group. И теперь посмотрим сколько времени было затрачено на выполнение кода: 7.01 секунда.

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

Заключение

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

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

Exit mobile version