Go中按次序交替打印1212...,你知道它背后的设计模式吗

本文通过具体的Go语言代码示例,展示了如何利用两个goroutine实现并发安全的生产者消费者模式,确保按顺序交替输出1212...并探讨了背后的原理。

聊聊后端面试那些事

原文

让你goroutine交替打印1212…-[公粽号:堆栈future]

首先这道题看似是两个goroutine交替打印,实则是有很多细节需要考虑,而且背后的设计模式就是生产者消费者模式

代码演示下简单的生产者消费者模式:

package main

import "fmt"

func main() {
 done := make(chan bool) //控制结束退出的信号
 ch := make(chan int) //chan通信 控制同步阻塞的

 go func() {
  for i:=1; i<=2; i++ {
   //生产
   ch <- 0
   fmt.Println("1")
  }

  //发送结束之后退出
  done <- true
 }()

 go func() {
  for i:=1; i<=2; i++ {
   //消费
   <- ch
   fmt.Println("2")
  }
 }()
 <-done
}

输出:
1
1
2
2

但是题目是让两个goroutine交替输出1212…而且还必须保证次序,比如A goroutine是永远打印1,B goroutine永远打印2 这样循环2次,输出结果应该是1212。

那么为什么上面的代码输出是1122呢?因为我们控制了开始的次序,但是并没有控制结束的次序,发生了并发不安全的情况。

当然你不能说这种方式就不适合别的场景,比如交替打印奇偶数,就增加一个条件判断就行:

package main

import "fmt"

func main() {
 done := make(chan bool) //控制结束退出的信号
 ch := make(chan int) //chan通信 控制同步阻塞的

 go func() {
  for i:=1; i<=5; i++ {
   //生产
   ch <- 0
   if i % 2 == 0 {
    fmt.Println("A ", i)
   }
  }

  //发送结束之后退出
  done <- true
 }()

 go func() {
  for i:=1; i<=5; i++ {
   //消费
   <- ch
   if i % 2 != 0 {
    fmt.Println("B ", i)
   }
  }
 }()
 <-done
}

输出:
B  1
A  2
B  3
A  4

B永远输出奇数,A永远输出偶数,它们交替打印。

好了言归正传,那我们如何写出并发安全的生产者消费者代码呢?我们用首尾两个chan交替释放控制权就可以解决这个问题,请看代码:

package main

import "fmt"

func main() {
 done := make(chan bool) //控制结束退出的信号
 chStart := make(chan int) //chan通信 控制起始同步阻塞的
 chEnd := make(chan int) //chan通信 控制结尾同步阻塞的

 go func() {
  for i:=1; i<=2; i++ {
   //生产 控制起始
   chStart <- 0
   fmt.Println("A ", 1)
   //暂停 等待结尾信号
   <-chEnd
  }

  //发送结束之后退出
  done <- true
 }()

 go func() {
  for i:=1; i<=2; i++ {
   //消费 结束起始
   <- chStart
   fmt.Println("B ", 2)

   //发送结尾信号
   chEnd <- 0
  }
 }()
 
 //阻塞等待退出信号
 <-done
}

输出:
A  1
B  2
A  1
B  2

现在可以看出来,输出的结果是正确的。为什么这么操作呢?就是当生产者生产之后把自己阻塞,等待消费者消费,消费者消费完成之后发送信号唤醒生产者继续生产,而消费者自己又被阻塞等生产者唤醒,其实这就是典型的生产者消费者模式。

小结

用两个chan实现按次序交替非常有意思,它背后的设计模式生产者消费者模式,大家可以在项目中使用起来,如果你觉得这篇文章对你有帮助,欢迎关注点赞转发哦。

- END -

公粽号:堆栈future

使很多处于迷茫阶段的coder能从这里找到光明,堆栈创世,功在当代,利在千秋

图片

该请求属于“题目型”问题,涉及**二叉树的遍历与复制算法设计**。 --- ### **题目重述** 1. 设计一个算法,按照**前序遍历顺序**打印二叉树中的所有**叶子结点**。 2. 设计一个算法,**复制一棵二叉树**,返回新树的根结点。 假设二叉树结点结构如下(以C语言风格描述): ```c struct TreeNode { char data; // 数据域 struct TreeNode *lchild; // 左孩子指针 struct TreeNode *rchild; // 右孩子指针 }; ``` --- ### **详解** #### **问题1:按前序次序打印二叉树中的叶子结点** ##### **思路分析** - 前序遍历顺序:根 → 左子树 → 右子树 - 叶子结点:左右孩子均为 `NULL` - 只需在前序遍历过程中,判断当前访问的结点是否为叶子,若是则打印 ##### **算法步骤** 1. 若当前结点为空,直接返回; 2. 判断当前结点是否为叶子(`lchild == NULL && rchild == NULL`),若是,输出其数据; 3. 递归遍历左子树; 4. 递归遍历右子树。 > 注意:虽然称为“前序”,但只有在确认是叶子时才输出,非叶子不输出。 ##### **代码实现(伪代码)** ```c void PrintLeaves_PreOrder(TreeNode *T) { if (T == NULL) return; // 如果是叶子结点,则打印 if (T->lchild == NULL && T->rchild == NULL) { printf("%c ", T->data); } // 前序:先处理根(已做),再左,后右 PrintLeaves_PreOrder(T->lchild); PrintLeaves_PreOrder(T->rchild); } ``` ##### **示例** 对于二叉树: ``` A / \ B C / / \ D E F ``` 叶子结点:D、E、F 前序访问顺序:A → B → D → C → E → F 其中叶子:D、E、F 输出:`D E F` --- #### **问题2:复制一棵二叉树** ##### **思路分析** - 使用递归思想:复制当前根结点,并递归复制其左、右子树; - 每个新结点动态分配内存,数据相同,左右孩子指向新复制的子树; - 空树返回 `NULL`。 ##### **算法步骤** 1. 若原树为空,返回 `NULL`; 2. 创建新结点,复制当前结点的数据; 3. 递归复制左子树,作为新结点的左孩子; 4. 递归复制右子树,作为新结点的右孩子; 5. 返回新结点。 ##### **代码实现(伪代码)** ```c TreeNode* CopyTree(TreeNode *T) { if (T == NULL) return NULL; // 创建新结点 TreeNode *newNode = (TreeNode*)malloc(sizeof(TreeNode)); newNode->data = T->data; // 递归复制左右子树 newNode->lchild = CopyTree(T->lchild); newNode->rchild = CopyTree(T->rchild); return newNode; } ``` ##### **说明** - 时间复杂度:$O(n)$,每个结点访问一次; - 空间复杂度:$O(h)$,递归栈深度,$h$ 为树高; - 复制结果为**结构完全相同的独立二叉树**,无共享结点。 --- ### **知识点** 1. **前序遍历应用**:在遍历过程中加入条件判断,可筛选特定类型结点(如叶子)并输出。 2. **叶子结点判定**:一个结点为叶子当且仅当其左、右孩子指针均为空(`lchild == NULL && rchild == NULL`)。 3. **二叉树复制**:通过递归方式重建整棵树,每层创建新结点并连接其子树,实现深拷贝。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值