Почему и Java, и Go блокируют сокет перед записью?

Код Go internal/poll/fd_unix.go находится здесь

// Write implements io.Writer.
func (fd *FD) Write(p []byte) (int, error) {
    if err := fd.writeLock(); err != nil {
        return 0, err
    }
    defer fd.writeUnlock()
    ......
}

код Java java.net.SocketOutputStream#socketWrite находится здесь

 private void socketWrite(byte b[], int off, int len) throws IOException {
        if (len <= 0 || off < 0 || len > b.length - off) {
            if (len == 0) {
                return;
            }
            throw new ArrayIndexOutOfBoundsException("len == " + len
                    + " off == " + off + " buffer length == " + b.length);
        }

        FileDescriptor fd = impl.acquireFD();
        try {
            socketWrite0(fd, b, off, len);
        } catch (SocketException se) {
        ......

Я не знаю, почему мы должны запирать это. Другой вопрос, syscall.Write эквивалентен записи <unistd.h> в C?

А что бы вы хотели, чтобы два потока вызывали этот метод одновременно?

Hulk 10.12.2020 09:07

Однажды я написал аналогичный код на C, используя 4 потока для записи в сокет, но последовательность, считанная другим потоком, верна, поэтому я думаю, что этот write системный вызов сам по себе является потокобезопасным.

tiddar Z 10.12.2020 09:16

Можете ли вы гарантировать это для всех возможных поддерживаемых платформ сейчас или в будущем? Задокументировано ли такое поведение или это просто деталь реализации?

Slaw 10.12.2020 09:28

Кажется, я понимаю. Что вы имеете в виду, что возможно, что write некоторых платформ не гарантирует аналогичные характеристики атомарности?

tiddar Z 10.12.2020 09:42

«Другой вопрос: syscall.Write эквивалентен записи <unistd.h> в C?» -- Это должен быть отдельный вопрос.

Jonathan Hall 10.12.2020 09:47
Этот ответ особенно актуален, хотя и говорит о немного другой ситуации (но основные принципы в основном те же).
Jonathan Hall 10.12.2020 09:56

@Flimzy Да, этот вопрос мне тоже помог. Спасибо

tiddar Z 10.12.2020 11:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
7
94
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Java-приложение хочет написать: «Привет! Как дела?».

Приложение Go хочет написать: «Увидимся позже».

Оба этих приложения пытаются писать одновременно.

Ожидаемый результат:

Hello! How are you?
See you later.

Тем не менее, есть большая вероятность, что вы получите что-то подобное.

HellSee o! How  See you are later.
 you?

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

спасибо за ответ, но я знаю, что если нет защиты/блокировки сокета/fd, последовательность байтов записи будет беспорядочной. Я, наверное, понимаю, что в ядре есть буфер записи при записи в какой-то сокет, разве это не буфер потокобезопасный?

tiddar Z 10.12.2020 09:48

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

Adrian 10.12.2020 15:28
Ответ принят как подходящий

Ну, только отвечая за часть Java:

Поскольку мы уже говорим о деталях реализации, зачем останавливаться на уровне Java? Исходный код C для OpenJdk реализации нативного метода socketWrite0 занимает около 70 строк кода и явно не является атомарным. Он выполняет такие вещи, как выделение и освобождение памяти с помощью malloc и free, среди прочего, и включает довольно много нетривиальной логики. На данный момент уже не имеет значения, будет ли функция NET_Send, которую он вызывает для фактической отправки данных, напрямую транслироваться в системный вызов на каждой поддерживаемой платформе.

Суть в том, что реализация вызывает эту NET_Send-функцию в цикле. Таким образом, независимо от того, является ли это потокобезопасным по отдельности, если он вызывается одновременно несколькими потоками, вывод будет чередоваться (в лучшем случае).

ооо, чем вы так много ,я узнал об этом через ваш ответ, и пойти тоже должно быть причиной. Потому что я обнаружил, что Go также циклически вызывает syscall.Write в приведенном ниже коде. for { max := len(p) if fd.IsStream && max-nn> maxRW { max = nn + maxRW } n, err := ignoringEINTR(func() (int, error) {return syscall.Write(fd.Sysfd, p[nn:max]) }) if n> 0 { nn += n }

tiddar Z 10.12.2020 10:14

Другие вопросы по теме