分享|go语言并发编程题汇总+解析(更新完毕)
1501
2024.04.13
2024.04.17
发布于 未知归属地
  1. 前言

  2. 题目

    1. 睡眠排序

      1. 题目描述
      2. 题目解析
    2. 计数器

      1. 题目描述
      2. 题目解析
    3. 打印0与奇偶数

      1. 题目描述
      2. 题目解析

前言

网络上有丰富的八股文资料,算法资料。但是针对go语言并发编程的题目和题解就很稀缺了。
ggboy还在春招中,本贴保证一天一更,持续到ggboy的春招结束为止。题解采用channel进行goroutine之间的通信

下面的题目,一部分来自力扣多线程题目的改编,一部分来自我个人面试以及网络中搜集到的并发编程题,希望对大家有所帮助。

题目

睡眠排序

题目描述

睡眠排序是一种神奇的排序算法。数值越大,睡眠的时间越久。元素醒来的顺序,就是数值从小到大的顺序。请你用协程和通道实现睡眠排序。

题目解析

假设数组中有n个数,就开启n个协程,每个协程负责对元素进行睡眠。数值越大,就让他睡眠越久。
思路比较简单,因此直接看代码实现

package main

import (
	"fmt"
	"time"
)

func main() {
	arr := []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
	ch := make(chan int, len(arr))
	for i := 0; i < len(arr); i++ {
		go sleep(arr[i], ch)
	}
	brr := make([]int, len(arr))
	for i := 0; i < len(arr); i++ {
		x := <-ch
		brr[i] = x
	}

	for _, x := range brr {
		fmt.Println(x)
	}
}

func sleep(i int, ch chan int) {
	time.Sleep(time.Duration(i) * time.Millisecond)
	ch <- i
}

计数器

题目描述

设计一个程序,创建并启动十个并发 Goroutine(轻量级线程),每个 Goroutine 具有唯一的标识符(ID),它们独立地对同一个整型变量 counter 进行自增操作。每个 Goroutine 在完成自增操作后,打印出其 ID 以及操作后的 counter 值。

题目解析

当一个G进行操作的时候,其他的G阻塞就好。

开辟一个空间为1的channel,当其中一个G进行操作的时候,其他G就会被阻塞。执行完操作,再给channel添加一个信号即可。

package main

import (
	"fmt"
	"sync"
)

var value = 0

func main() {
	ch := make(chan struct{}, 1)
	wg := sync.WaitGroup{}
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			<-ch
			value++
			fmt.Printf("协程%d操作之后的value为%d\n", id, value)
			ch <- struct{}{}
		}(i)
	}

	ch <- struct{}{}
	wg.Wait()
}

打印0与奇偶数

题目描述

编写一个名为 ZeroEvenOdd 的 Go 语言结构体,它包含三个方法:zero, even, 和 odd。这些方法将被传递给三个不同的 Goroutine(Go 语言中的轻量级线程),每个 Goroutine 分别负责输出特定类型的数字:

  • Goroutine A: 调用 zero() 方法,仅输出数字 0

  • Goroutine B: 调用 even() 方法,仅输出偶数。

  • Goroutine C: 调用 odd() 方法,仅输出奇数。

目标是通过协调这三个 Goroutine 的工作,使得它们共同输出一个由数字组成的序列,格式为 "010203040506...",且序列的总长度为 2n

具体实现要求如下:

  1. 构造函数
  • 定义一个 ZeroEvenOdd 结构体的构造函数 NewZeroEvenOdd(n int),它接受一个整数 n 作为参数,用于初始化一个 ZeroEvenOdd 实例。这个整数 n 表示需要输出的数的个数,因此最终序列的长度应为 2n
  1. 方法定义
  • 对于 ZeroEvenOdd 结构体,实现以下方法:
  1. 并发执行
  • 创建一个 ZeroEvenOdd 实例,并将其传递给三个不同的 Goroutine。每个 Goroutine 应分别调用对应的 zero, even, 或 odd 方法,确保它们按照正确的顺序交错执行,最终生成并输出期望的序列。

示例中的 printNumber 函数已提供,它接受一个整数参数并将其输出到控制台。例如,调用 printNumber(7) 将打印出数字 7

请确保你的实现能够正确地协调三个 Goroutine 的工作,以按照指定顺序输出完整的序列,且序列的总长度为 2n

package main

import (
	"fmt"
)

type ZeroEvenOdd struct {
	n int
}

func (z *ZeroEvenOdd) zero(printNumber func(int)) {

}

func (z *ZeroEvenOdd) even(printNumber func(int)) {

}

func (z *ZeroEvenOdd) odd(printNumber func(int)) {

}

func PrintNumber(i int) {
	fmt.Print(i)
}

func Constructor(n int) *ZeroEvenOdd {

}

题目解析

观察示例发现,奇数个0的后面,就跟奇数;第偶数个0后面跟偶数
因此枚举1到n,枚举到奇数个0,就可以打印奇数;枚举到偶数个0,打印偶数

同样,打印完奇数或者偶数,就可以打印0。

因此协程执行的顺序为 zero -> even/odd -> zero

协程之间的通信用channel,这里开辟三个channel来实现zero和odd的通信,zero和even的通信

思路很简单,因此具体实现看代码即可。

package main

import (
	"fmt"
	"sync"
)

type ZeroEvenOdd struct {
	n             int
	ch0, ch1, ch2 chan struct{}
}

func (z *ZeroEvenOdd) zero(printNumber func(int)) {
	defer wg.Done()
	for i := 0; i < z.n; i++ {
		select {
		case <-z.ch0:
			printNumber(0)
			if i%2 == 0 {
				z.ch1 <- struct{}{}
			} else {
				z.ch2 <- struct{}{}
			}
		}
	}
}

func (z *ZeroEvenOdd) even(printNumber func(int)) {
	defer wg.Done()
	for i := 1; i <= z.n; i++ {
		if i%2 == 1 {
			continue
		}
		select {
		case <-z.ch2:
			printNumber(i)
			if i == z.n {
				return
			}
			z.ch0 <- struct{}{}
		}
	}

}

func (z *ZeroEvenOdd) odd(printNumber func(int)) {
	defer wg.Done()
	for i := 1; i <= z.n; i++ {
		if i%2 == 0 {
			continue
		}
		select {
		case <-z.ch1:
			printNumber(i)
			if i == z.n {
				return
			}

			z.ch0 <- struct{}{}
		}
	}

}

func PrintNumber(i int) {
	fmt.Print(i)
}

func Constructor(n int) *ZeroEvenOdd {
	return &ZeroEvenOdd{n: n, ch0: make(chan struct{}, 0), ch1: make(chan struct{}, 0), ch2: make(chan struct{}, 0)}
}

var wg = sync.WaitGroup{}

func main() {
	z := Constructor(10)
	wg.Add(3)
	go z.zero(PrintNumber)
	go z.even(PrintNumber)
	go z.odd(PrintNumber)
	z.ch0 <- struct{}{}

	wg.Wait()

}
评论 (2)