• 【Java&Go并发编程系列】4.等待一组并发任务完成——CountDownLatch VS sync.WaitGroup


    说明:Java & Go 并发编程序列的文章,根据每篇文章的主题或细分标题,分别演示 Java 和 Go 语言当中的相关实现。更多该系列文章请查看:Java & Go 并发编程系列

    本文介绍 Java 和 Go 语言中如何实现并发编程中等待一组并发任务完成的场景。

    代码场景:假设有一道题目,同时分发给10个同学来完成,所有同学完成之后,老师公布答案。

    「Java」CountDownLatch

    使用 CountDownLatch 等待一组并发任务的完成,包含如下三要素:

    • 一个初始值,即定义需要等待的任务的数目
    • await() 需要等待并发任务先完成的线程调用
    • countDown(),每个任务在完成的时候调用
    public static void main(String[] args) throws InterruptedException {
    
        // 声明初始计数为10
        CountDownLatch countDownLatch = new CountDownLatch(10);
    
        // 起10个线程模拟10个同学做题目
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(() -> {
                // 生成从1-10的随机整数,模拟做题目花费的时间
                int randomSecond = ThreadLocalRandom.current().nextInt(10) + 1;
                try {
                    TimeUnit.SECONDS.sleep(randomSecond);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.printf("第%s号同学用时%ds完成了题目
    ", Thread.currentThread().getName(), randomSecond);
                // 计数减1
                countDownLatch.countDown();
            });
            // 设置线程名称,用于打印对应的同学编号
            t.setName(String.valueOf(i));
            t.start();
        }
    
        // 阻塞直到 CountDownLatch 的计数为0
        // 注意这里是 await 方法, 不是 wait 方法
        countDownLatch.await();
        System.out.printf("所有的同学都完成了题目,老师公布答案
    ");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    输出结果:

    第2号同学用时2s完成了题目
    第9号同学用时2s完成了题目
    第8号同学用时2s完成了题目
    第3号同学用时2s完成了题目
    第5号同学用时4s完成了题目
    第6号同学用时6s完成了题目
    第1号同学用时8s完成了题目
    第0号同学用时9s完成了题目
    第4号同学用时9s完成了题目
    第7号同学用时9s完成了题目
    所有的同学都完成了题目,老师公布答案
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    「Go」sync.WaitGroup

    使用 sync.WaitGroup 等待一组并发任务的完成,包含如下三要素:

    • Add(),传入一个 int 类型的参数,增加计数器的数值
    • Wait(),需要等待并发任务先完成的 goroutine 调用
    • Done(),每个任务在完成的时候调用
    func main() {
    	// sync.WaitGroup 是开箱即用的类型
    	var wg sync.WaitGroup
    	// 初始化计数器的值为10
    	wg.Add(10)
    	// 启用10个 goroutine 用于并发执行 go 函数 
    	// goroutine,即用户级线程,也称协程,是 Go 语言并发编程模型中重要的三个元素 GPM 之一
    	for i := 0; i < 10; i++ {
    		go func(i int) {
    			randomSecond := rand.Intn(10) + 1
    			time.Sleep(time.Second * time.Duration(randomSecond))
    			fmt.Printf("第%d号同学用时%ds完成了题目
    ", i, randomSecond)
    			// 计数器减1
    			wg.Done()
    		}(i)
    	}
    	// 阻塞直到所有并发任务执行完成
    	wg.Wait()
    	fmt.Printf("所有的同学都完成了题目,老师公布答案
    ")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    为了让程序的逻辑更严密,可以使用 defer 语句来保证 wg.Done() 一定被执行。

    func main() {
    	var wg sync.WaitGroup
    	wg.Add(10)
    	for i := 0; i < 10; i++ {
    		go func(i int) {
    			// defer 语句用于延迟执行代码,在函数正常或异常结束的时刻执行
    			// 类似于 Java 的 finally
    			defer wg.Done()
    			randomSecond := rand.Intn(10) + 1
    			time.Sleep(time.Second * time.Duration(randomSecond))
    			fmt.Printf("第%d号同学用时%ds完成了题目
    ", i, randomSecond)
    		}(i)
    	}
    	wg.Wait()
    	fmt.Printf("所有的同学都完成了题目,老师公布答案
    ")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出结果:

    第2号同学用时1s完成了题目
    第0号同学用时1s完成了题目
    第6号同学用时2s完成了题目
    第1号同学用时2s完成了题目
    第8号同学用时6s完成了题目
    第4号同学用时7s完成了题目
    第5号同学用时8s完成了题目
    第9号同学用时8s完成了题目
    第7号同学用时9s完成了题目
    第3号同学用时10s完成了题目
    所有的同学都完成了题目,老师公布答案
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    拓展

    「Java」CyclicBarrier

    CyclicBarrier 可传入另一个 Runnable 对象,在所有线程都到达集合点后,将运行该 Runnable 对象,使用场景更广泛。

    public static void main(String[] args) {
    
        // 声明10参与者和结束时要运行的 Runnable 对象
        // 这里只是打印了一行文字
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10,
                () -> System.out.printf("所有的同学都完成了题目,老师公布答案
    ")
        );
    
        // 起10个线程模拟10个同学做题目
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(() -> {
                // 生成从1-10的随机整数,模拟做题目花费的时间
                int randomSecond = ThreadLocalRandom.current().nextInt(10) + 1;
                try {
                    TimeUnit.SECONDS.sleep(randomSecond);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.printf("第%s号同学用时%ds完成了题目
    ", Thread.currentThread().getName(), randomSecond);
                try {
                    // 阻塞直到所有参与者都调用 await 方法
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
            // 设置线程名称
            t.setName(String.valueOf(i));
            t.start();
        }
    
        System.out.printf("主线程执行结束
    ");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    输出结果:

    主线程执行结束
    第1号同学用时1s完成了题目
    第5号同学用时1s完成了题目
    第0号同学用时2s完成了题目
    第3号同学用时4s完成了题目
    第9号同学用时4s完成了题目
    第2号同学用时5s完成了题目
    第6号同学用时5s完成了题目
    第4号同学用时6s完成了题目
    第7号同学用时8s完成了题目
    第8号同学用时10s完成了题目
    所有的同学都完成了题目,老师公布答案
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    更多该系列文章请查看:Java & Go 并发编程系列

  • 相关阅读:
    [国嵌攻略][121][中断分层设计]
    [国嵌攻略][120][按键驱动硬件操作实现]
    [国嵌攻略][119][Linux中断处理程序设计]
    [国嵌攻略][118][混杂设备模型]
    神乎其技
    [国嵌攻略][117][LED驱动程序设计]
    [国嵌攻略][116][字符设备控制技术]
    JavaScript中的匿名函数及函数的闭包(转)
    【总结整理】IFeatureBuffer
    【总结整理】webGIS学习
  • 原文地址:https://www.cnblogs.com/xiami2046/p/13936911.html
Copyright © 2020-2023  润新知