1.2 Go学习笔记之线程

GoRoutines

helloworld

  • 轻量级的线程执行
package main

import (
    "fmt"
    "time"
)

func f(from string) {
    for i := 0; i < 3; i++ {
        fmt.Println(from, ":", i)
    }
}

func main() {


//1
    go f("goroutine")
//2
    go func(msg string) {
        fmt.Println(msg)
    }("going")
//1和2交错执行,并行执行
    time.Sleep(time.Second)
    fmt.Println("done")
}

channels

  • 用于管道通信
package main

import "fmt"

func main() {

//创建管道,类型同传递的数据类型
    messages := make(chan string)
//<-语法传递值
    go func() { messages <- "ping" }()
//接收值
    msg := <-messages
    fmt.Println(msg)
}
  • 发送和接收都会阻塞,直到双方都ready,不需要其他同步机制

By default sends and receives block until both the sender and receiver are ready. This property allowed us to wait at the end of our program for the "ping" message without having to use any other synchronization.

channel Buffering

  • 默认管道是unbuffered,只有双方都ready才行,带buffered的管道能接收有限数量的values而不需要对应的receiver

By default channels are unbuffered, meaning that they will only accept sends (chan <-) if there is a corresponding receive (<- chan) ready to receive the sent value. Buffered channels accept a limited number of values without a corresponding receiver for those values.

package main

import "fmt"

func main() {
//2为缓冲区大小
    messages := make(chan string, 2)
//Because this channel is buffered, we can send these values into the channel 
//without a corresponding concurrent receive
    messages <- "buffered"
    messages <- "channel"

    fmt.Println(<-messages) //接收数据
    fmt.Println(<-messages)
}

channel同步(重点)

  • 通过管道来同步不同goroutines的执行
  • an example of using a blocking receive to wait for a goroutine to finish
  • 如果要多个任务并行执行完成再执行下面任务,建议使用WaitGroup
package main

import (
    "fmt"
    "time"
)

func worker(done chan bool) { //done管道用于通知另一个goroutine这个task已经完成
    fmt.Print("working...")
    time.Sleep(time.Second)
    fmt.Println("done")

    done <- true //send a value来通知该task完成了
}

func main() {

    done := make(chan bool, 1)
    go worker(done)   //开启任务,传入channel来通知完成与否

    <-done //阻塞,直到收到管道来的完成信号
    //如没有上文,则可能task还没完成就退出程序了
}

channel方向(need check)

  • 当channel作为函数参数,可以指定管道只接收或者只发送,增加程序的类型安全性
package main

import "fmt"
//pings这个channel只能发送值
func ping(pings chan<- string, msg string) {
    pings <- msg
}
//pings只接收,pongs只发送
func pong(pings <-chan string, pongs chan<- string) {
    msg := <-pings
    pongs <- msg
}

func main() {
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)
}

select

  • 让你在多个管道的operations下等待

lets you wait on multiple channel operations

  • select多用于将goroutines和channels相结合
func main() {

    c1 := make(chan string)
    c2 := make(chan string)

//模拟同时运行的多个阻塞式RPC操作
//simulate blocking RPC operations 
//executing in concurrent goroutines
    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()

    for i := 0; i < 2; i++ {
    //使用select来await管道来的值
    //当其到达(值OK,即阻塞时间到达)时则print
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}

received one
received two
//case明明只有一条道,能都print的原因是for循环
//整个程序耗时2秒,因为能同时执行两个阻塞操作

Timeouts

  • 基于管道和select来实现timeout
package main

import (
    "fmt"
    "time"
)

func main() {
//采用带有缓存的channel避免没有reader而一直阻塞,学名叫防止goroutines leaks
//Note that the channel is buffered
//so the send in the goroutine is nonblocking. 
//This is a common pattern to prevent goroutine leaks in case the channel is never read.
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "result 1"
    }()

    select {
    case res := <-c1:
        fmt.Println(res)
    //<-time.After awaits a value to be sent 
    //after the timeout of 1s    
    //相当于设置1s的timeout
    case <-time.After(1 * time.Second):
        fmt.Println("timeout 1")
    }

    c2 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "result 2"
    }()
    select {
    case res := <-c2:
        fmt.Println(res)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout 2")
    }
}

Output:
timeout 1
result 2

非阻塞式管道操作

  • 基于select和default语法来实现非阻塞式接收和发送,甚至非阻塞式的multi-way selects
package main

import "fmt"

func main() {
    messages := make(chan string)
    signals := make(chan bool)

//非阻塞式接收
//若管道有值,则第一个case,否则立刻走default case
    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    default:
        fmt.Println("no message received")
    }
//非阻塞式发送实例
//此处msg无法发送出去,因为channel没有接收者且channel没有buffer
//故执行default case
    msg := "hi"
    select {
    case messages <- msg:
        fmt.Println("sent message", msg)
    default:
        fmt.Println("no message sent")
    }
//基于多个case来实现multi-way non-blocking select
//此处非阻塞式接收messages和signals这两个channel的内容
    select {
    case msg := <-messages:
        fmt.Println("received message", msg)
    case sig := <-signals:
        fmt.Println("received signal", sig)
    default:
        fmt.Println("no activity")
    }
}

no message received
no message sent
no activity

关闭channels

  • 关闭channel意味着无法通过channel来send值,用于和管道的接收者来通信
func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)

    go func() {
        for {
            j, more := <-jobs
            //不断接收jobs
            //more只有当管道关闭且所有管道内的值被接收才为false
            if more {
                fmt.Println("received job", j)
            } else {
            //通过done来通知所有job已经做完了
                fmt.Println("received all jobs")
                done <- true
                return
            }
        }
    }()

    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    //交代完三个工作后,关闭管道
    close(jobs)
    fmt.Println("sent all jobs")
    //等待worker来完成job,基于GR间的同步方法,参照上文
    <-done
}

sent job 1
received job 1
sent job 2
received job 2
sent job 3
received job 3
sent all jobs
received all jobs
  • 使用jobs这个管道来在main这个GR和worker这个GR间通信,来得知工作是否完成

range over channels

  • 利用range来在channel上iterate
package main

import "fmt"

func main() {

    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"
    close(queue)
//This range iterates over each element as it’s received from queue
//Because we closed the channel above
//the iteration terminates after receiving the 2 elements
    for elem := range queue {
        fmt.Println(elem)
    }
}
  • 该例也表明在关闭非空管道后仍然能去接收还未接收的值

it’s possible to close a non-empty channel but still have the remaining values be received

Timers

  • 适用场景:schedule式执行或周期执行
  • Timers代表未来的一个单一事件,入口参数为等待时间,它会提供一个channel,在该时间点来通知
func main() {

    timer1 := time.NewTimer(2 * time.Second)
//一直block直到timer的channel有数据发送过来
//此种场景可以用sleep来替代
    <-timer1.C
    fmt.Println("Timer 1 fired")
//timer的好处在于可以中途cancel
    timer2 := time.NewTimer(time.Second)
    go func() {
        <-timer2.C
        fmt.Println("Timer 2 fired")
    }()
    //中途cancel,所以Timer 2 fired不会print出来
    stop2 := timer2.Stop()
    if stop2 {
        fmt.Println("Timer 2 stopped")
    }

    time.Sleep(2 * time.Second)
}

Timer 1 fired
Timer 2 stopped

Tickers

  • 用于周期性任务的执行
package main

import (
    "fmt"
    "time"
)

func main() {

    ticker := time.NewTicker(500 * time.Millisecond)
    done := make(chan bool)

    go func() {
        for {
            select {
            case <-done:
                return
//use the select builtin on the channel to await the values as they arrive every 500ms
//Tickers同timers利用管道来发送值
            case t := <-ticker.C:
                fmt.Println("Tick at", t)
            }
        }
    }()

    time.Sleep(1600 * time.Millisecond)
    //stop则对应的channel将不能接收任何值
    ticker.Stop()
    done <- true
    fmt.Println("Ticker stopped")
}

Tick at 2012-09-23 11:29:56.487625 -0700 PDT
Tick at 2012-09-23 11:29:56.988063 -0700 PDT
Tick at 2012-09-23 11:29:57.488076 -0700 PDT
Ticker stopped

Worker Pools

package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("worker", id, "started  job", j)
        time.Sleep(time.Second)
        fmt.Println("worker", id, "finished job", j)
        //模拟工作内容
        results <- j * 2
    }
}

func main() {

    const numJobs = 5
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
   //创建三个worker和对应发送work和worker来发送results的管道
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
//发送5个job
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    //关闭管道,表明上述5个job就是全部工作内容
    close(jobs)
    //收集工作results
    for a := 1; a <= numJobs; a++ {
    //block住,除非接收5个job的result后
        <-results
    }
}


Our running program shows the 5 jobs being executed by various workers. The program only takes about 2 seconds despite doing about 5 seconds of total work because there are 3 workers operating concurrently.

WaitGroups

  • 类比Java的CountDownLatch
package main

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

//a WaitGroup must be passed to functions by pointer
func worker(id int, wg *sync.WaitGroup) {
//On return, notify the WaitGroup that we’re done.
    defer wg.Done() //相当于finally内执行countDown()

    fmt.Printf("Worker %d starting\n", id)

    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {

    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
// increment the WaitGroup counter for each    
        wg.Add(1)  //初始值为5
        go worker(i, &wg)  //5个worker线程
    }
//Block until the WaitGroup counter goes back to 0; all the workers notified they’re done.
    wg.Wait() //等待count为0
}

Rate Limiting(重要)

  • 控制资源利用率
package main

import (
    "fmt"
    "time"
)

func main() {

//We’ll serve these requests off a channel of the same name
    requests := make(chan int, 5)
    for i := 1; i <= 5; i++ {
        requests <- i
    }
    close(requests)
    
//利用limiter这个tick来作为rate limiting的regulator,每200秒能接收一次值
    limiter := time.Tick(200 * time.Millisecond)

    for req := range requests {
    //基于limiter这一管道的receive阻塞来控制每200秒处理一次request
        <-limiter
        fmt.Println("request", req, time.Now())
    }
//以下实例利用burstLimiter这个channel来实现非均匀的rate limit
    burstyLimiter := make(chan time.Time, 3)
/fill up the channel to represent allowed bursting
    for i := 0; i < 3; i++ {
        burstyLimiter <- time.Now()
    }
//Every 200 milliseconds we’ll try to add a new value to burstyLimiter, up to its limit of 3
    go func() {
        for t := range time.Tick(200 * time.Millisecond) {
            burstyLimiter <- t
        }
    }()

    burstyRequests := make(chan int, 5)
//Now simulate 5 more incoming requests. 
//The first 3 of these will benefit from the burst capability of burstyLimiter    
    for i := 1; i <= 5; i++ {
        burstyRequests <- i
    }
    close(burstyRequests)
    for req := range burstyRequests {
        <-burstyLimiter
        fmt.Println("request", req, time.Now())
    }
}

request 1 2012-10-19 00:38:18.687438 +0000 UTC
request 2 2012-10-19 00:38:18.887471 +0000 UTC
request 3 2012-10-19 00:38:19.087238 +0000 UTC
request 4 2012-10-19 00:38:19.287338 +0000 UTC
request 5 2012-10-19 00:38:19.487331 +0000 UTC

request 1 2012-10-19 00:38:20.487578 +0000 UTC
request 2 2012-10-19 00:38:20.487645 +0000 UTC
request 3 2012-10-19 00:38:20.487676 +0000 UTC
request 4 2012-10-19 00:38:20.687483 +0000 UTC
request 5 2012-10-19 00:38:20.887542 +0000 UTC

Atomic Counters

  • 关注怎么在Go中管理状态
  • 关注利用 sync/atomic 这个包 for atomic counters accessed by multiple goroutines
package main

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

func main() {

    var ops uint64

    var wg sync.WaitGroup

    for i := 0; i < 50; i++ {
        wg.Add(1)

        go func() {
            for c := 0; c < 1000; c++ {
//To atomically increment the counter we use AddUint64, 
//giving it the memory address of our ops counter with the & syntax
                atomic.AddUint64(&ops, 1)
            }
            wg.Done()
        }()
    }

    wg.Wait()
//Reading atomics safely while they are being updated is also possible
//using functions like atomic.LoadUint64
    fmt.Println("ops:", ops)
}

ops: 50000

Had we used the non-atomic ops++ to increment the counter, we’d likely get a different number, changing between runs, because the goroutines would interfere with each other. Moreover, we’d get data race failures when running with the -race flag

  • 如果不用原子类,则会每次数字不同,因为线程间的相互干扰

Mutexes

  • 上文利用原子操作来管理简单的counter state
  • 对于复杂情况,需要safely来获取state需要基于mutex
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "sync/atomic"
    "time"
)

func main() {

    var state = make(map[int]int)
//This mutex will synchronize access to state
    var mutex = &sync.Mutex{}
//记录读写次数
    var readOps uint64
    var writeOps uint64
//start 100 goroutines to execute repeated reads against the state
//once per millisecond in each goroutine
    for r := 0; r < 100; r++ {
        go func() {
            total := 0
            for {

                key := rand.Intn(5)
                mutex.Lock()
                //加锁来确保exclusive access
                total += state[key]
                mutex.Unlock()
                //读一次就加一次
                atomic.AddUint64(&readOps, 1)

                time.Sleep(time.Millisecond)
            }
        }()
    }
//类比读操作
    for w := 0; w < 10; w++ {
        go func() {
            for {
                key := rand.Intn(5)
                val := rand.Intn(100)
                mutex.Lock()
                state[key] = val
                mutex.Unlock()
                atomic.AddUint64(&writeOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }

    time.Sleep(time.Second)
//显示最终的读写次数
    readOpsFinal := atomic.LoadUint64(&readOps)
    fmt.Println("readOps:", readOpsFinal)
    writeOpsFinal := atomic.LoadUint64(&writeOps)
    fmt.Println("writeOps:", writeOpsFinal)
//With a final lock of state, show how it ended up
    mutex.Lock()
    fmt.Println("state:", state)
    mutex.Unlock()
}

stateful goroutines(重要)

  • 上文我们显式地用mutex锁来进行同步操作(不同线程中访问shared state)
  • 本文用channel和线程来实现(?)
    • 基于通信中的共享内存
    • 只有一个goroutines拥有某data(保证数据不会有并发访问问题),其他线程要访问需要和该线程通信

This channel-based approach aligns with Go’s ideas of sharing memory by communicating and having each piece of data owned by exactly 1 goroutine.

package main

import (
    "fmt"
    "math/rand"
    "sync/atomic"
    "time"
)
//基于readOp和writeOp结构来封装对共享数据的请求和响应
//These readOp and writeOp structs encapsulate those requests 
//and a way for the owning goroutine to respond.
type readOp struct {
    key  int
    resp chan int
}
type writeOp struct {
    key  int
    val  int
    resp chan bool
}

func main() {
//用于对读写计数
    var readOps uint64
    var writeOps uint64
//下面的两个管道用来其他线程来发布读写请求
    reads := make(chan readOp)
    writes := make(chan writeOp)

    go func() {
    //state是个map,仅被一个线程拥有
        var state = make(map[int]int)
        for {
        //利用select来对读写请求进行响应
            select {
            case read := <-reads:
                read.resp <- state[read.key]
            case write := <-writes:
                state[write.key] = write.val
                write.resp <- true
            }
        }
    }()
//启动100个读线程
    for r := 0; r < 100; r++ {
        go func() {
            for {
            //每个线程都创建一个结构体对象
                read := readOp{
                    key:  rand.Intn(5),
                    resp: make(chan int)}
                reads <- read
                <-read.resp
                atomic.AddUint64(&readOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }
	//同理,同上
    for w := 0; w < 10; w++ {
        go func() {
            for {
                write := writeOp{
                    key:  rand.Intn(5),
                    val:  rand.Intn(100),
                    resp: make(chan bool)}
                writes <- write
                <-write.resp
                atomic.AddUint64(&writeOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }

    time.Sleep(time.Second)

    readOpsFinal := atomic.LoadUint64(&readOps)
    fmt.Println("readOps:", readOpsFinal)
    writeOpsFinal := atomic.LoadUint64(&writeOps)
    fmt.Println("writeOps:", writeOpsFinal)
}
  • 与上文方法的比较:

It might be useful in certain cases though, for example where you have other channels involved or when managing multiple such mutexes would be error-prone.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值