高并发时代下的设计模式-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来实现并发式的生产者消费者模式的确是更为清爽了。

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值