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

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

Пример: создание простых чисел

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

package main

import (
    "fmt"
    "math/rand"
    "os"
    "strconv"
)

func isPrime(n int) bool {
    if n < 2 {
        return false
    }
    for i := 2; i*i <= n; i++ {
        if n%i == 0 {
            return false
        }
    }
    return true
}

func primeWorker(in <-chan int, out chan<- int) {
    for n := range in {
        if isPrime(n) {
            out <- n
        }
    }
    close(out)
}

func saveWorker(out <-chan int) {
    file, err := os.Create("primes.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    for n := range out {
        _, err := fmt.Fprintf(file, "%d\n", n)
        if err != nil {
            panic(err)
        }
    }
}

func main() {
    rand.Seed(42)
    numbers := make(chan int)
    primes := make(chan int)

    // start the prime worker
    go primeWorker(numbers, primes)

    // start the save worker
    go saveWorker(primes)

    // generate random numbers
    for i := 0; i < 1000; i++ {
        numbers <- rand.Intn(10000)
    }
    close(numbers)

    // wait for workers to finish
    for range primes {
        // do nothing, just wait
    }

    fmt.Println("Done")
}

В этом примере основная процедура Go генерирует 1000 случайных чисел и отправляет их по каналуnumbers. Горутина primeWorker получает числа, проверяет, являются ли они простыми, и отправляет результат в канал primes. Наконец, подпрограмма saveWorker go получает простые числа и сохраняет их в файл.

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

Защита параллельного кода в Go

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

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

func saveWorker(out <-chan int) {
    file, err := os.Create("primes.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    for n := range out {
        if n < 10000 && n > 1 {
            _, err := fmt.Fprintf(file, "%d\n", n)
            if err != nil {
                panic(err)
            }
        } else {
            fmt.Printf("Invalid prime number: %d\n", n)
        }
    }
}

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

Во-вторых, горутинаprimeWorker получает данные из ненадежного источника (каналnumbers). Отправляя неверные данные, злоумышленник может отправить много данных или вызвать сбой программы. Чтобы смягчить это, мы должны ограничить объем данных, которые могут быть отправлены, установив максимальный размер для буфера numberschannel или используя ограниченный канал. Это гарантирует, что подпрограмма primeWorker go не получит больше данных, чем она может обработать, и помогает предотвратить сбой или замедление программы из-за исчерпания ресурсов.

Вот пример того, как вы можете изменить канал numbers, чтобы ограничить размер его буфера:

numbers := make(chan int, 100) // buffer size of 100

Это создает буферизованный канал с максимальным размером буфера 100. Если буфер заполнен, любые дальнейшие попытки отправки данных по каналу будут заблокированы до тех пор, пока в буфере не будет свободного места.

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

numbers := make(chan int, 100) // capacity of 100

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

Заключение

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

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

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