В чем разница между асинхронностью и параллельностью?

Ответ

Эти термины часто путают, но они описывают разные аспекты выполнения задач:


  • Асинхронность (Asynchrony): Это про управление задачами, чтобы не блокировать основной поток выполнения. Асинхронная операция запускается, и управление немедленно возвращается вызывающей стороне, позволяя ей выполнять другие задачи, пока асинхронная операция работает в фоне. Результат операции обычно обрабатывается через колбэки, промисы, каналы или другие механизмы уведомления о завершении. Асинхронность позволяет достичь конкурентности (concurrency) — способности системы обрабатывать несколько задач одновременно, не обязательно выполняя их в один и тот же момент.



  • Параллельность (Parallelism): Это про одновременное выполнение нескольких задач. Параллельность требует наличия нескольких вычислительных ресурсов (например, нескольких ядер CPU или процессоров), на которых задачи могут выполняться физически одновременно. Если у вас одно ядро CPU, вы можете иметь асинхронность (переключение между задачами), но не истинную параллельность.


Ключевое отличие:

  • Асинхронность — это стратегия выполнения (неблокирующая), позволяющая достичь конкурентности.
  • Параллельность — это фактическое одновременное выполнение задач.

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

Пример асинхронности в Go (конкурентность):

В Go горутины (goroutines) являются основным механизмом для асинхронного выполнения. Они позволяют запускать функции в фоне, не блокируя основной поток.

package main

import (
    "fmt"
    "time"
)

func asyncTask() {
    time.Sleep(2 * time.Second) // Имитация долгой операции
    fmt.Println("Async task finished")
}

func main() {
    fmt.Println("Main started")
    go asyncTask() // Запускаем задачу асинхронно
    fmt.Println("Main continues immediately")
    time.Sleep(3 * time.Second) // Даем время асинхронной задаче завершиться
    fmt.Println("Main finished")
}

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

Пример параллельности в Go:

Go-планировщик может выполнять горутины параллельно на доступных ядрах CPU. Для этого не требуется явного указания, но можно влиять на количество используемых ядер с помощью runtime.GOMAXPROCS (хотя в современных версиях Go это редко требуется).

package main

import (
    "fmt"
    "runtime"
    "sync"
    "time"
)

func parallelTask(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Task %d started on CPU %dn", id, runtime.GOMAXPROCS(0))
    time.Sleep(1 * time.Second)
    fmt.Printf("Task %d finishedn", id)
}

func main() {
    // Устанавливаем максимальное количество ядер для использования (по умолчанию - количество логических ядер)
    // runtime.GOMAXPROCS(runtime.NumCPU()) // Обычно не требуется явно устанавливать

    var wg sync.WaitGroup

    fmt.Println("Launching tasks...")

    wg.Add(2)
    go parallelTask(1, &wg) // Эти две горутины могут выполняться параллельно
    go parallelTask(2, &wg) // на разных ядрах CPU, если они доступны

    wg.Wait()
    fmt.Println("All tasks finished")
}

В этом примере, если у вас есть несколько ядер CPU, parallelTask(1) и parallelTask(2) могут выполняться одновременно, демонстрируя параллельность. Go-планировщик автоматически распределяет горутины по доступным потокам ОС, которые затем выполняются на ядрах CPU.