初探Golang协程

84 阅读2分钟

Go相比于老牌的Java,最大的特点无疑是原生支持高并发,而高并发支持的基点则来自于协程。

线程与协程

以Java为例,Java原生只有多线程的支持而没有协程(在Java19中引入了虚拟线程的概念),因此Java的并发能力完全依赖于线程。其每建立一个线程就需要付出约1MB左右的内存,对于资源昂贵的服务器来说线程无疑是一种昂贵的资源。

与此相对的,Go中的协程则非常的轻量,每一个协程只需要约几十KB的内存即可提供完整的服务。因此同时开启数十万个协程是可以实现的,而这对于线程来说则是不可想象的。

传统线程的并发模式

在传统的并发模式中,程序对于线程的应用必须小心翼翼,利用线程池小心的规划资源的使用,并且要时常注意对共享资源加锁以保证数据的合法性。同时在线程之间进行切换也有着相当高昂的成本。

协程并发

对于协程来说,抛弃掉了线程池管理的成本,协程能够自动调度。同时协程之间的通信更加简单,只需通过channel进行通信即可,避免了锁竞争。协程的资源占用更少且切换效率更高。 以分别计算1000个数求和再求综合的为例

class SumThread implements Runnable {
  int[] array;
  int start;
  int end;
  Result result;
  
  SumThread(int[] array, int start, int end, Result result) {
    this.array = array;
    this.start = start; 
    this.end = end;
    this.result = result;
  }

  public void run() {
    int sum = 0;
    for (int i = start; i < end; i++) {
      sum += array[i];
    }
    result.addResult(sum); 
  }
}

class Result {
  int total = 0;
  
  public synchronized void addResult(int sum) {
    total += sum; 
  }
}

public class Main {
  public static void main(String[] args) {
    int[] array = {1, 2, 3, ..., 1000};
    Result result = new Result();
    List<Thread> threads = new ArrayList<>();
    int numThreads = 10;
    int segment = array.length / numThreads;
    
    for (int i = 0; i < numThreads; i++) {
      int start = i * segment;
      int end = (i+1) * segment;
      Thread t = new Thread(new SumThread(array, start, end, result));
      threads.add(t);
      t.start();
    }
    
    for (Thread t : threads) {
      t.join();
    }
    
    System.out.println(result.total);
  }
}
package main

import (
    "fmt"
    "sync"
)

func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // 将结果发送到通道
}

func main() {
    list := []int{1, 2, 3, ..., 1000} // 1000个数字
    
    c := make(chan int, 10) 
    var wg sync.WaitGroup
    for i:= 0; i< 10; i++ {
        wg.Add(1)
        go sum(list[i*100:(i+1)*100], c) //启动10个goroutine并发求和
    }
    
    go func() {
        wg.Wait() 
        close(c)
    }()
    
    var total int
    for i := range c {
        total += i
    }
    fmt.Println(total) // 结果
}

两段代码的差距显而易见,go实现起来比Java更简单且结构更加清晰,并且还不用处理锁的问题。