网络上有丰富的八股文资料,算法资料。但是针对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()
}
编写一个名为 ZeroEvenOdd 的 Go 语言结构体,它包含三个方法:zero, even, 和 odd。这些方法将被传递给三个不同的 Goroutine(Go 语言中的轻量级线程),每个 Goroutine 分别负责输出特定类型的数字:
Goroutine A: 调用 zero() 方法,仅输出数字 0。
Goroutine B: 调用 even() 方法,仅输出偶数。
Goroutine C: 调用 odd() 方法,仅输出奇数。
目标是通过协调这三个 Goroutine 的工作,使得它们共同输出一个由数字组成的序列,格式为 "010203040506...",且序列的总长度为 2n。
具体实现要求如下:
ZeroEvenOdd 结构体的构造函数 NewZeroEvenOdd(n int),它接受一个整数 n 作为参数,用于初始化一个 ZeroEvenOdd 实例。这个整数 n 表示需要输出的数的个数,因此最终序列的长度应为 2n。ZeroEvenOdd 结构体,实现以下方法: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()
}