第六届字节跳动青训营第二课 | 青训营

84 阅读3分钟

第二节课之Go语言并发

  1. 并发与并行 并发指的是多线程程序在一个核的CPU上交替运行,而并行则指多线程程序在多个核的CPU上分别运行,其目的都是为了表现得像同时运行程序的不同线程。

  2. 协程与线程 在Go语言开发中,主要使用协程Goroutine来实现并发。其中,协程是一种用户态的轻量级线程,调度完全由用户控制,可以无需加锁访问,因而上下文切换更快。 正如一个线程可以有多个协程,一个进程也可以单独拥有多个协程;线程进程都是同步机制,而协程则是异步机制。协程能保留上一次调用的状态,每次重入时,就重新进入上一次调用的状态,以保证上下文切换的前后一致。

  3. 通道 Go语言的并发,通过通信共享内存而非通过共享内存实现通信。在Go语言中,即利用channel通道,实现在两个Goroutine之间传递一个指定类型的值来同步运行和通讯。 默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据,此时发送方会被阻塞直到某个接收方获取到一个值。而在创建通道时,可以增加一个参数指定缓冲区大小,对于带缓冲区的通道,发送方则会阻塞直到发送的值被拷贝到缓冲区内;若缓冲区已满,则发送方需等待有接收方获取到缓冲区内的值释放空间。

  4. 共享锁与 sync 在并发实现中,也有一种锁机制,即在访问某一数据时,不能同时有多个线程访问,否则会造成数据更改丢失或数据不一致的问题,这就需要在访问数据时加锁以免被额外访问。 sync.Mutex 实现了 Lock()Unlock() 方法,保证数据同时只能有一个锁存在。 sync.RWMutex 则是一个读写互斥锁,除了 Lock()Unlock() 方法,还具备 RLock()RUnlock() 方法,可以实现并发读取,但一般仅在频繁读取和不频繁写入的场景里运用。 sync.WaitGroup 则用于同步,可以让程序等待一组协程全部执行完成后再执行后续代码,避免协程执行完成的先后顺序影响程序运行效果。

  5. 示例代码1 此代码为协程的使用示例,此函数会分别创建两个通道。之后再运行两个协程,一个负责向 src 通道里传递数据,另一个则负责从 src 数据里遍历取出数据并向 dest 通道传递。最后一层循环是遍历 dest 通道的数据将其打印。

    func calSquare() {
        src := make(chan int)
        dest := make(chan int, 3)
    
        go func() {
            defer close(src)
            for i := 0; i < 10; i++ {
                src <- 1
            }
        }()
        go func() {
            defer close(dest)
            for i := range src {
                dest <- i * i
            }
        }()
    
        for i := range dest {
            println(i)
        }
    }
    
  6. 示例代码2 本示例代码演示了锁的运用。在不启用锁时,因为有2000个协程都要对数据执行加1操作,极可能存在并发操作的时刻,最后的结果会小于2000不符预期。而有了加锁放锁操作的保障,最后的结果则是稳定在2000,符合预期。

    var (
        x    int64
        lock sync.Mutex
    )
    
    func addWithLock() {
        for i := 0; i < 2000; i++ {
            lock.Lock()
            x += 1
            lock.Unlock()
        }
    }
    
    func addWithoutLock() {
        for i := 0; i < 2000; i++ {
            x += 1
        }
    }
    
    func Add() {
        x = 0
        for i := 0; i < 5; i++ {
            go addWithoutLock()
        }
        time.Sleep(time.Second)
        println("WithoutLock:", x)
    
        x = 0
        for i := 0; i < 5; i++ {
            go addWithLock()
        }
        time.Sleep(time.Second)
        println("WithLock", x)
    }