Cách sử dụng package `sync` của golang
golang
49
Go
50
White

Hoàng Minh Trung viết ngày 28/06/2016

Golang mang tiếng là một ngôn ngữ lập trình bất đồng bộ rất tốt, tuy nhiên nếu sử dụng không quen thì sẽ rất khó để làm code bạn chạy thật sự bất đồng bộ. Việc sử dụng thành thạo package sync sẽ làm thao tác bất đồng bộ dễ dàng hơn nhiều, do đó mà ở bài này mình sẽ giới thiệu về cách sử dụng package này.

sync.Mutex

Đây có lẽ là cái được sử dụng nhiều nhất trong package này
Ví dụ mình có đoạn code sau

package main

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

func parallel(wg *sync.WaitGroup) {
    fmt.Println("A")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("B")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("C")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("D")
    wg.Done()
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    wg := new(sync.WaitGroup)
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go parallel(wg)
    }
    wg.Wait()
}

Kết quả sẽ là

A
A
A
B
B
B
C
C
C
D
D
D

Kết quả trên sẽ theo thứ tự lộn xộn, không theo mong muốn là A,B,C,D. Khi đó bạn sử dụng mutex sẽ giải quyết vấn đề

package main

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

func parallel(wg *sync.WaitGroup, m *sync.Mutex) {
    m.Lock()
    defer m.Unlock()

    fmt.Println("博")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("多")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("の")
    time.Sleep(100 * time.Millisecond)
    fmt.Println("塩")
    wg.Done()
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    wg := new(sync.WaitGroup)
    m := new(sync.Mutex)
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go parallel(wg, m)
    }
    wg.Wait()
}

Khi đó kết quả sẽ như mong muốn

A
B
C
D
...

Ở đoạn code trên, nếu thay vì Add(1) mà Add(n) thì sau khi gọi wg.Wait() đoạn code sẽ kết thúc khi Done() được gọi n lần

sync.Atomic

Ví dụ về sync atomic

package main

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

var v int32

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    wg := new(sync.WaitGroup)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            v++
            v++
            v++
            v++
            v++
            v++
            v++
            v++
            v++
            v++
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(v)
}

Đoạn code trên kì vọng kết quả sẽ là 100, nhưng thực ra lại đôi khi không có kết quả như mong đợi đó. Lý do bởi vì biến v không phải atomic nên sẽ bị overwrite giá trị nhiều lần.

Để sử dụng atomic chúng ta chỉ cần đơn giản viết lại như dưới đây

package main

import (
    "fmt"
    "runtime"
    "sync"
    "sync/atomic"
)

var v int32

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    wg := new(sync.WaitGroup)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            atomic.AddInt32(&v, 1)
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(v)
}

sync.Once

Khi bạn có một xử lý mà chỉ muốn thực hiện một lần duy nhất, ví dụ như sau

package main

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

var once = new(sync.Once)

func greeting(wg *sync.WaitGroup) {
    once.Do(func() {
        fmt.Println("foo")
    })

    fmt.Println("bar")
    wg.Done()
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    defer fmt.Println("bye")

    wg := new(sync.WaitGroup)
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go greeting(wg)
    }
    wg.Wait()
}

Kết quả đoạn code trên sẽ là

foo
bar
bar
bar
bar
bar
bye

Đoạn code in ra foo dù được thực hiện song song nhưng chỉ được execute đúng một lần

sync.Cond

Giả sử chúng ta có 10 cái goroutine, khi có một điều kiện nào đó xảy ra thì sẽ chạy từng cái một bất đồng bộ. Trong trường hợp đó thì bạn chỉ cần tạo sync.Cond và gọi Signal() lần lượt

package main

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

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    l := new(sync.Mutex)
    c := sync.NewCond(l)
    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Printf("waiting %d\n", i)
            l.Lock()
            defer l.Unlock()
            c.Wait()
            fmt.Printf("go %d\n", i)
        }(i)
    }

    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Second)
        c.Signal()
    }
    time.Sleep(1 * time.Second)
}

Khi đó kết quả sẽ là

waiting 0
waiting 9
waiting 5
waiting 1
waiting 2
waiting 3
waiting 4
waiting 8
waiting 6
waiting 7
go 0
go 9
go 5
go 1
go 2
go 3
go 4
go 8
go 6
go 7

Ngoài ra có hàm BroadCast() cũng khá tiện

package main

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

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    l := new(sync.Mutex)
    c := sync.NewCond(l)
    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Printf("waiting %d\n", i)
            l.Lock()
            defer l.Unlock()
            c.Wait()
            fmt.Printf("go %d\n", i)
        }(i)
    }

    for i := 3; i >= 0; i-- {
        time.Sleep(1 * time.Second)
        fmt.Println(i)
    }
    c.Broadcast()
    time.Sleep(3 * time.Second)
}

Kết quả sẽ là

waiting 0
waiting 1
waiting 4
waiting 7
waiting 8
waiting 9
waiting 5
waiting 2
waiting 3
waiting 6
3
2
1
0
go 0
go 9
go 1
go 4
go 7
go 8
go 3
go 5
go 2
go 6

sync.Pool

Hàm này rất tiện khi bạn muốn mix giữa xử lý đồng bộ và bất đồng bộ

package main

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

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    p := sync.Pool{
        New: func() interface{} {
            return "fix time work"
        },
    }

    wg := new(sync.WaitGroup)

    wg.Add(1)
    go func() {
        for i := 0; i < 10; i++ {
            p.Put("interrupt work")
            time.Sleep(100 * time.Millisecond)
        }
        wg.Done()
    }()

    wg.Add(1)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(p.Get())
            time.Sleep(50 * time.Millisecond)
        }
        wg.Done()
    }()

    wg.Wait()
}

Kết quả sẽ là

fix time work
fix time work
interrupt work
fix time work
interrupt work
interrupt work
fix time work
fix time work
interrupt work
fix time work
Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Hoàng Minh Trung

23 bài viết.
61 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
19 1
Bài viết dịch từ http://arslan.io/tenusefultechniquesingo Sử dụng một GOPATH duy nhất Sử dụng đồng thời nhiều GOPATH sẽ không giúp cho hệ thống ...
Hoàng Minh Trung viết 2 năm trước
19 1
White
18 15
(Ảnh) Mục đích của bài viết là hướng dẫn cơ bản nhất cho những ai chưa biết về docker, môi trường thực hiện là mac OS. Chuẩn bị Cài đặt virtua...
Hoàng Minh Trung viết gần 3 năm trước
18 15
White
14 0
Bài viết dịch từ https://github.com/luciotato/golangnotes/blob/master/OOP.md Mục đích bài viết Học golang dễ dàng hơn với những kiến thức bạn đ...
Hoàng Minh Trung viết 2 năm trước
14 0
Bài viết liên quan
White
16 0
Crawl dữ liệu Crawl là một vấn đề hay gặp trong quá trình làm software. Ví dụ lấy tin tức, tin giảm giá, vé xem phim... là những dạng của crawl. Mộ...
Thach Le viết hơn 2 năm trước
16 0
White
44 7
Là một người thường xuyên đọc Quora, có một điểm cá nhân tôi thấy rất ấn tượng ở quora chính là khả năng autocomplete với tốc độ ánh sáng. (Ảnh) ...
huydx viết 7 tháng trước
44 7
White
9 2
Makefile thực hiện một số thao tác thường dùng trong Go Khi làm project Go mình thường tạo một file Makefile dạng này: Lưu ý nhớ thay thành tên m...
Huy Trần viết 2 năm trước
9 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
23 bài viết.
61 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!