千家信息网

Thread和goroutine两种方式怎样实现共享变量按序输出

发表于:2026-11-04 作者:千家信息网编辑
千家信息网最后更新 2026年11月04日,这期内容当中小编将会给大家带来有关Thread和goroutine两种方式怎样实现共享变量按序输出,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。背景最近在看go的一
千家信息网最后更新 2026年11月04日Thread和goroutine两种方式怎样实现共享变量按序输出

这期内容当中小编将会给大家带来有关Thread和goroutine两种方式怎样实现共享变量按序输出,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

背景

最近在看go的一些底层实现,其中印象最为深刻的是go语言创造者之一Rob Pike说过的一句话,不要通过共享内存通信,而应该通过通信来共享内存,其中这后半句话对应的实现是通道(channel),利用通道在多个协程(goroutine)之间传递数据。看到这里,我不禁产生了一个疑问,对于无状态数据之间的传递,通过通道保证数据之间并发安全没什么问题,但我现在有一个临界区或者共享变量,存在多线程并发访问。Go协程如何控制数据并发安全性?难道还有其它高招?带着这个疑问,我们看看Go是如何保证临界区共享变量并发访问问题。

下面我们通过一个经典的题目来验证线程和协程分别是如何解决的。

有三个线程/协程完成如下任务:1线程/协程打印1,2线程/协程打印2,3线程/协程打印3,依次交替打印15次。输出:123123123123123

java实现

java对于这个问题如何解决呢?首先要求依次输出,那么只要保证线程互相等待或者说步调一致即可实现上述问题。

如何实现步调一致呢?我知道的方法至少有三种,以下我通过三种实现方式来介绍Java线程是如何控制临界区共享变量并发访问。

Synchronized实现

通过Synchronized解决互斥问题; (wait/notifyAll)等待-通知机制控制多个线程之间执行节奏。实现方式如下:

public class Thread123 {

public static void main(String[] args) throws InterruptedException {
Thread123 testABC = new Thread123();

Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
testABC.printA();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
testABC.printB();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
testABC.printC();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
}
int flag = 1;
public synchronized void printA() throws InterruptedException {
while (flag != 1) {
this.wait();
}
System.out.print(flag);
flag = 2;
this.notifyAll();
}

private synchronized void printB() throws InterruptedException {
while (flag != 2) {
this.wait();
}
System.out.print(flag);
flag = 3;
this.notifyAll();
}

private synchronized void printC() throws InterruptedException {
while (flag != 3) {
this.wait();
}
System.out.print(flag);
flag = 1;
this.notifyAll();
}
}

看到这段实现可能大家都会有如下两个疑问:

  • 为啥要用notifyAll,而没有使用notify?

"

这两者其实是有一定区别的,notify是随机的通知等待队列中的一个线程,而notifyAll是通知等待队列中所有的线程。可能我们第一感觉是即使使用了notifyAll也是只能有一个线程真正执行,但是在多线程编程中,所谓的感觉都蕴藏着风险,因为有些线程可能永远也不会被唤醒,这就导致即使满足条件也无法执行,所以除非你很清楚你的线程执行逻辑,一般情况下,不要使用notify。有兴趣的话,上面例子,可以测试下,你就可以得知为什么不建议你用notify。

"
  • 为啥要用while循环,而不是用更轻量的if?

"

利用while的原因,从根本上来说是java中的编程范式,只要涉及到wait等待,都需要用while。原因是因为当wait返回时,有可能判断条件已经发生变化,所以需要重新检验条件是否满足。

"

Lock实现

通过Lock解决多线程之间互斥问题; (await/signal)解决线程之间同步,当然这种实现方式和上一种效果是一样的。

public class Test {

// 打印方式跟上一种方式一样,这里不在给出。
private int flag = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();

private void print1() {
try {
lock.lock();
while (flag != 1) {
try {
this.condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("A");
flag = 2;
this.condition2.signal();
}finally {
lock.unlock();
}

}


private void print2() {
try {
lock.lock();
while (flag != 2) {
try {
this.condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("B");
flag = 3;
this.condition3.signal();
}finally {
lock.unlock();
}

}

private void print3() {
try {
lock.lock();
while (flag != 3) {
try {
this.condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("C");
flag = 1;
this.condition1.signal();
}finally {
lock.unlock();
}
}

Semaphore实现

信号量获取和归还机制来保证共享数据并发安全,以下为部分核心代码;

// 以s1开始的信号量,初始信号量数量为1
private static Semaphore s1 = new Semaphore(1);
// s2、s3信号量,s1完成后开始,初始信号数量为0
private static Semaphore s2 = new Semaphore(0);
private static Semaphore s3 = new Semaphore(0);
static class Thread1 extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
s1.acquire();// s1获取信号执行,s1信号量减1,当s1为0时将无法继续获得该信号量
System.out.print("1");
s2.release();// s2释放信号,s2信号量加1(初始为0),此时可以获取B信号量
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

其实除了以上方法,用CountDownLatch实现多个线程互相等待应该也是可以解决的,这里不在过多举例。

Go实现

在用Go的实现过程中,主要用到了三个知识点。1、先后启用了三个goroutine对共享变量进行操作; 2、一把互斥锁产生的三个条件变量对三个协程进行控制; 3、使用signChannel目的是为了不让goroutine过早结束运行。


package main

import (
"log"
"sync"
)

func main() {
//声明共享变量
var flag = 1
//声明互斥锁
var lock sync.RWMutex
//三个条件变量,用于控制三个协程执行频率
cnd1 := sync.NewCond(&lock)
cnd2 := sync.NewCond(&lock)
cnd3 := sync.NewCond(&lock)
//创建一个通道,用于控制goroutine过早结束运行
signChannel := make(chan struct{}, 3)
//最大循环次数
max := 5

go func(max int) {
//本次goroutine执行完成之后释放
defer func() {
signChannel <- struct{}{}
}()
//循环执行
for i := 1; i <= max; i++ {
// 锁定本次临界环境变量修改
lock.Lock()
//通过for循环检测条件是否发生变化,类似于上面的while
for flag != 1 {
//等待
cnd1.Wait()
}
//输出
log.Print(flag)
//修改标识,释放锁、并对其它协程发送信号
flag = 2
lock.Unlock()
cnd2.Signal()
}
}(max)

go func(max int) {
defer func() {
signChannel <- struct{}{}
}()
for i := 1; i <= max; i++ {
lock.Lock()
for flag != 2 {
cnd2.Wait()
}
log.Print(flag)
flag = 3
lock.Unlock()
cnd3.Signal()
}
}(max)

go func(max int) {
defer func() {
signChannel <- struct{}{}
}()
for i := 1; i <= max; i++ {
lock.Lock()
for flag != 3 {
cnd3.Wait()
}
log.Print(flag)
flag = 1
lock.Unlock()
cnd1.Signal()
}
}(max)

<- signChannel
<- signChannel
<- signChannel

}

可以看出这种实现方式也是通过锁和条件变量来控制临界区,这跟线程中Lock、await/signal实现方式没什么区别。(这是初次学习Go中互斥锁这块知识时,根据自己理解,编写的一种实现方式,如有问题,请多指教或者留言指正)

通过如上加锁和条件变量的机制解决了临界区变量并发安全问题,我们知道,之所以会如上出现并发问题,从源头上来说是硬件开发人员给软件开发人员挖的一个坑,为了提高并发性能,计算机出现了多核CPU,为了提高运算速度,CPU中又添加了高速缓存,这就导致多个CPU在做计算的时候缓存不能共享、交替执行,从而出现并发问题,无论线程、还是协程、解决思路很简单,通过加锁、禁用CPU缓存、公用内存。当然还存在编译优化带来的指令重排序问题,要想彻底解决必须从编程语言层面保证原子性 、有序性。无论如何处理,要想保证临界区变量的安全,总会存在一定性能损耗。

上述就是小编为大家分享的Thread和goroutine两种方式怎样实现共享变量按序输出了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。

线程 变量 信号 问题 方式 条件 信号量 三个 控制 之间 保证 输出 安全 数据 多个 通道 循环 内存 机制 疑问 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 南京欣网互联网络科技有限公司 互联网属科技 互联网科技的 杭州互联网科技公司 无线互联网科技 菲比寻常互联网科技 互联网科技产品 全球顶尖互联网科技公司 互联网软件和科技 智腾互联网科技有限公司 花神(南京)互联网科技有限公司 互联网科技公司有风险吗 链环科技传统企业怎么转型互联网 互联网科技公司营业执照怎么办理 华中科技大学互联网的牛人 图游互联网科技 科技和互联网行业的特点 绵阳真晨互联网科技有限公司 互联网科技创新方面的资料 武汉亿视互联网科技有限公司 互联网科技工作委员会毛军 上海互联网科技有限公司 唐信互联网科技 广西拼得多互联网科技有限公司 成都互联网科技公司100强 速维有品互联网信息科技(苏州)有限公司 徐州市互联网科技有限公司 工业互联网山东科技大学 互联网科技快讯热点 江苏互联网智能科技产品出厂价
0