高併發時代下的設計模式-GO和JAVA的對比

在這個高併發時代最重要的設計模式無疑是生產者、消費者模式,比如著名的消息隊列kafka其實就是一個巨型的生產者消費者模式的實現。生產者消費者問題,也稱有限緩衝問題,是一個併發環境編程的經典案例。生產者生成一定量的產品放到庫房,並不斷重複此過程;與此同時,消費者也在緩衝區消耗這些數據,但由於庫房大小有限,所以生產者和消費者之間步調協調,生產者不會在庫房滿的情況放入端口,消費者也不會在庫房空時消耗數據。詳見下圖:

 

 

而從GO語言併發模型來看,利用channel確實能達到共享內存的目的,因爲channel的性質和一塊帶有讀寫狀態且保證數據順序的共享內存並無二致。但通過前面的介紹讀者也許也能發現,消息隊列的封裝程度明顯可以做的更高,因此GO語言之父們才說會要通過通信來共享內存。

爲了幫助大家找到區別,我們先以Java爲例來看一下沒有channel的情況下,生產者消費者如何實現。Java的代碼及註釋如下:

public class Storage {
 
    // 倉庫最大存儲量
    private final int MAX_SIZE = 10;
    // 倉庫存儲的載體
    private LinkedList<Object> list = new LinkedList<Object>();
    // 鎖
    private final Lock lock = new ReentrantLock();
    // 倉庫滿的信號量
    private final Condition full = lock.newCondition();
    // 倉庫空的信號量
    private final Condition empty = lock.newCondition();
 
    public void produce()
    {
        // 獲得鎖
        lock.lock();
        while (list.size() + 1 > MAX_SIZE) {
            System.out.println("【生產者" + Thread.currentThread().getName()
		             + "】倉庫已滿");
            try {
                full.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        list.add(new Object());
        System.out.println("【生產者" + Thread.currentThread().getName() 
				 + "】生產一個產品,現庫存" + list.size());
 
        empty.signalAll();
        lock.unlock();
    }
 
    public void consume()
    {
        // 獲得鎖
        lock.lock();
        while (list.size() == 0) {
            System.out.println("【消費者" + Thread.currentThread().getName()
		             + "】倉庫爲空");
            try {
                empty.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        list.remove();
        System.out.println("【消費者" + Thread.currentThread().getName()
		         + "】消費一個產品,現庫存" + list.size());
 
        full.signalAll();
        lock.unlock();
    }
}

 

在沒有channel的編程語言如JAVA中這種生產者、消費者模式至少要藉助一個lock和兩個信號量共同完成。其中鎖的作用是保證同是時間,倉庫中只有一個用戶進行數據的修改,而還需要表示倉庫滿的信號量,一旦達到倉庫滿的情況則將此信號量置爲阻塞狀態,從而阻止其它生產者再向倉庫運商品了,反之倉庫空的信號量也是一樣,一旦倉庫空了,也要阻其它消費者再前來消費了。

 

我們剛剛也介紹過了GO語言中的channel其實就是基於lock實現的循環隊列,所以不需要再添加lock和信號量就能實現模式了,以下代碼中我們通過子goroutine完成了生產者的功能,在主goroutine中實現了消費者的功能,注意channel的讀取必須放在不同的goroutine當中,輕而易舉的就這完成了生產者消費者模式。下面我們就通過具體實踐中來看一下生產者消費者模型的實現。

package main

import (
	"fmt"
)

func Product(ch chan<- int) { //生產者
	for i := 0; i < 3; i++ {
		fmt.Println("Product  produceed", i)
		ch <- i //由於channel是goroutine安全的,所以此處沒有必要必須加鎖或者加lock操作.
	}
}
func Consumer(ch <-chan int) {
	for i := 0; i < 3; i++ {
		j := <-ch //由於channel是goroutine安全的,所以此處沒有必要必須加鎖或者加lock操作.
		fmt.Println("Consmuer consumed ", j)
	}
}
func main() {
	ch := make(chan int)
	go Product(ch)
	Consumer(ch)
	/*運行結果爲
		Product  produceed 0
	Product  produceed 1
	Consmuer consumed  0
	Consmuer consumed  1
	Product  produceed 2
	Consmuer consumed  2
	*/

}

可以看到和Java比起來使用GO來實現併發式的生產者消費者模式的確是更爲清爽了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章