Dmitriy (r3code) wrote,
Dmitriy
r3code

Category:

GoLang: Ошибки конкурентных вычислений в Go










Прочитал статью Go: Concurrency Bugs in Go (Vincent Blanchon, 09.2019) в которой пишут о некоторых проблемах замеченных в реализации конкурентных вычислений в языке Go. Текущая версия Go 1.13.

Несколько человек из Пенсильванского госуниверситета в США провели исследование текущей ситуации и описали его в статье Real-World Concurrency Bugs in Go (Yiying Zhang и Linhai Song).

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

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

Что отмечено

В наблюдении отмечено что:

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

Мы призываем обратить внимание на эту проблему и начать исследование в области определения ошибок в этой области.
2. «Каналы» используются широко для исправления ошибок синхронизации не только связанных с самими каналами, но и с общей памятью.

Что предлагается улучшить?

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

Исходя из этих наблюдений и предположений они начали работу надо новым анализатором кода. Ziheng Liu отвечает за этот проект. Он основан на пакете SSA и уже доказал пользу обнаружив ошибку в проекте GraphQL Go. Выпуск этого анализатора запланирован на январь 2020 года.

Более подробная информация об исследовании в первоисточнике https://songlh.github.io/paper/go-study.pdf

Примеры ошибок и их исправления

- обозначает удаляемую строку, + добавляемую (исправление)

1. Блокировка вызванная каналом

func finishReq(timeout time.Duration) r ob {
  - ch :=make(chanob)
  + ch :=make(chanob, 1)
  gofunc() {
    result := fn()
    ch <- result // block
  } ()
  select{
    case result = <- ch:
      return result
    case <- time.After(timeout):
      return nil
  }
}



Код из Kubernetes. The finish Reqfunction creates a child goroutine using an anonymous function at line 4 to handle a request—a common practice in Go server programs. The child goroutine executes fn() and sends result back to the parent goroutine through channel chat line 6. The child will block at line 6 until the parent pulls result from chat line 9. Meanwhile, the parent will block at select until either when the child sends result toch(line 9) or when a timeout happens (line 11). If timeout happens earlier or if Go runtime (non-deterministically) chooses the case at line 11 when both cases are valid, the parent will return from requestReq() at line 12, and no one else can pull result from chany more, resulting in the child beingblocked forever. The fix is to change ch from an unbuffered channel to a buffered one, so that the child goroutine can always send the result even when the parent has exit.

2. Блокировка вызванная WaitGroup


1 var group sync.WaitGroup
2 group.Add(len(pm.plugins))
3 for_, p :=rangepm.plugins {
4   gofunc(p *plugin) {
5     defergroup.Done()
6 }
7- group.Wait()
8}
9+ group.Wait()




Docker#25384, happens with the use of a shared variable of type WaitGroup, as shown in above. The Wait() at line 7 can only be unblocked, when Done() at line 5 is invoked len(pm.plugins) times, since len(pm.plugins) is used as parameter to call Add() at line2. However, the Wait() is called inside the loop, so that it blocks goroutine creation at line 4 in later iterations and it blocks the invocation of Done() inside each created goroutine. The fix of this bug is to move the invocation of Wait() out from the loop.


3. Блокировка вызванная context


1 - hctx, hcancel := context.WithCancel(ctx)
2 +var hctx context.Context
3 +var hcancel context.CancelFunc
4 if timeout > 0 {
5 hctx, hcancel = context.WithTimeout(ctx, timeout)
6+ }else{
7+   hctx, hcancel = context.WithCancel(ctx)
8}





A new context object,hcancel, is created at line 1. A new goroutine iscreated at the same time, and messages can be sent to the newgoroutine through the channel field ofhcancel. Iftimeoutis larger than 0 at line 4, anothercontextobject is created atline 5, andhcancelis pointing to the new object. After that,there is no way to send messages to or close the goroutineattached to the old object. The patch is to avoid creating theextracontextobject whentimeoutis larger than 0.


4. Состояние гонки вызванное ананоимной функцией


Пример ошибки в Docker:
1 for i := 17; i <= 21; i++ { // write
2-       gofunc() { /* Create a new goroutine */
3+       gofunc(iint) {
4 apiVersion := fmt.Sprintf("v1.%d", i) // read
5 ... 
6-       }()
7+       }(i)
8}



Эта беспечная ошибка может быть легко исправлена запуском команды `go vet`.


Дполнительные примеры ошибок смотрите в статье об исследовании.
Tags: golang, ошибки, прочитано, синтаксический анализатор, синхронизация
Subscribe

Posts from This Journal “golang” Tag

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments