使goroutine同步的方法总结

前言:

在前面并发性能对比的文章中,我们可以看到Golang处理大并发的能力十分强劲,而且开发也特别方便,只需要用go关键字即可开启一个新的协程。

但当多个goroutine同时进行处理的时候,就会遇到同时抢占一个资源的情况(并发都会遇到的问题),所以我们希望某个goroutine等待另一个goroutine处理完某一个步骤之后才能继续。sync包就是为了让goroutine同步而出现的。当然还可以使用channel实现,这个后面会介绍到。

锁:

锁有两种:互斥锁(mutex)和读写锁(RWMutex)

互斥锁: 当数据被加锁了之后,除次外的其他协程不能对数据进行读操作和写操作。 这个当然能解决并发程序对资源的操作。但是,效率上是个问题,因为当加锁后,其他协程只有等到解锁后才能对数据进行读写操作。

读写锁: 读数据的时候上读锁,写数据的时候上写锁。有写锁的时候,数据不可读不可写。有读锁的时候,数据可读,不可写。

两种锁的使用方式相同,这里就只列出互斥锁的代码:

package main

import (
  "sync"
  "time"
  "fmt"
)

var num = 0

func main ()  {
  mu := &sync.Mutex{}
  for i:=0;i<10000;i++ {
    go func(){
      mu.Lock()
      defer mu.Unlock()
      num += 1
    }()
  }
  time.Sleep(time.Second)
  fmt.Println("num:", num)  // 如果不加锁这里的num的值会是一个随机数而不是10000
}

Once:

有的时候,我们启动多个相同goroutine,但是里面的某个操作我只希望被执行一次,这个时候Once就上场了。

package main

import (
  "fmt"
  "sync"
  "time"
)

func main() {
  var once sync.Once
  one := func() {
	fmt.Println("just once")
  }

  for i := 0; i < 10; i++ {
	go func(a int) {
	  once.Do(one)   // 只是被执行一次
	}(i)
  }
  time.Sleep(time.Millisecond*200)
}

WaitGroup:

当某个操作或是某个goroutine需要等待一批goroutine执行完毕以后才继续执行,那么这种多线程(go里面说的线程就是goroutine)等待的问题就可以使用WaitGroup了。

代码如下:

package main

import (
	"sync"
	"fmt"
	"time"
)

var waitGroup sync.WaitGroup

func main () {
	for i := 0; i < 10; i++ {
		waitGroup.Add(1)  // 添加需要等待goroutine的数量
		go func() {
			fmt.Println("hehe")
			time.Sleep(time.Second)
			waitGroup.Done() // 减少需要等待goroutine的数量 相当于Add(-1)
		} ()
	}

	waitGroup.Wait()  // 执行阻塞,直到所有的需要等待的goroutine数量变成0
	fmt.Println("over")
}

Cond:

sync.Cond是用来控制某个条件下,goroutine进入等待时期,等待信号到来,然后重新启动。

代码如下:

package main

import (
	"fmt"
	"sync"
	"time"
)
var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)

func test(x int) {
	cond.L.Lock() //获取锁
	cond.Wait()//等待通知 暂时阻塞
	fmt.Println(x)
	time.Sleep(time.Second * 1)
	cond.L.Unlock()//释放锁
}
func main() {
	for i := 0; i < 40; i++ {
		go test(i)
	}
	fmt.Println("start all")
	time.Sleep(time.Second * 3)
	fmt.Println("signal1")
	cond.Signal()   // 下发一个通知随机给已经获取锁的goroutine
	time.Sleep(time.Second * 3)
	fmt.Println("signal2")
	cond.Signal()// 下发第二个通知随机给已经获取锁的goroutine
	time.Sleep(time.Second * 1)  // 在广播之前要等一会,让所有线程都在wait状态
	fmt.Println("broadcast")
	cond.Broadcast()//下发广播给所有等待的goroutine
	time.Sleep(time.Second * 60)
}

上面代码有几个要点要特别说明一下:

1. 每个Cond都必须有个与之关联的锁  // 见第9行

2. 协程方法里面一开始/结束都必须加/解锁 // 见第12行和16行

3. cond.Wait()时会自动解锁,当被唤醒时,又会加上锁。所以第2点提到必须加/解锁。

Channel

channel不仅可以用来goroutine之间的通信,也可以使goroutine同步完成协作。这点主要基于从channel取数据的时候,会阻塞当前goroutine这个特性。示例代码如下:

package main

import (
	"fmt"
	"time"
)

var chan1 = make(chan string, 512)

var arr1 = []string{"qq","ww","ee","rr","tt"}

func chanTest1() {
	for _, v := range arr1 {
		chan1 <- v
	}
	close(chan1) // 关闭channel
}

func chanTest2() {
	for {
		getStr, ok := <- chan1  // 阻塞,直到chan1里面有数据
		if !ok {   // 判断channel是否关闭或者为空
			return
		}
		fmt.Println(getStr) // 按数组顺序内容输出
	}
}

func main () {
	go chanTest1()
	go chanTest2()

	time.Sleep(time.Millisecond*200)
}

原文地址:https://www.cnblogs.com/xiaoxlm/p/9753942.html

时间: 2024-10-12 23:15:28

使goroutine同步的方法总结的相关文章

同步方法 sleep和wait 线程同步的方法

当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 分两种情况 1):进入此对象的非同步方法 答案:可以 2):进入此对象的同步方法 答案:不可以 sleep指线程被调用时,占着CPU不工作,形象地说明为"占着CPU睡觉",此时,系统的CPU部分资源被占用,其他线程无法进入,会增加时间限制.wait指线程处于进入等待状态,形象地说明为"等待使用CPU",此时线程不占用任何资源,不增加时间限制.所以sleep(100L)意

JAVA中线程同步的方法(7种)汇总

一.同步方法 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态. 注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类. 二.同步代码块 即有synchronized关键字修饰的语句块. 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步 代码如: synchronized(object){ } 注:同步是一种高开销

线上一例主从不同步解决方法

首先,在从库上执行:show slave status\G ,发现SQL显示:NO,并且报错删除日志子类的. 解决方法:才从库上连续执行若干次如下命令即可解决: mysql>slave stop;              mysql>SET GLOBAL SQL_SLAVE_SKIP_COUNTER =1;     #跳过一个事务              mysql>slave start; 线上一例主从不同步解决方法,布布扣,bubuko.com

SQLServer2000数据同步复制技术方法

一. 预备工作1.发布服务器,订阅服务器都创建一个同名的windows用户,并设置相同的密码,做为发布快照文件夹的有效访问用户--管理工具--计算机管理--用户和组--右键用户--新建用户--建立一个隶属于administrator组的登陆windows的用户(SynUser)2.在发布服务器上,新建一个共享目录,做为发布的快照文件的存放目录,操作:我的电脑--D: 新建一个目录,名为: PUB--右键这个新建的目录--属性--共享--选择"共享该文件夹"--通过"权限&qu

oracle数据表数据同步公用方法

自己写了个数据同步的方法,两个数据库之间的数据同步,自己可以通过调用存储过程,添加作业实现定时同步数据. CREATE OR REPLACE PROCEDURE Data_sync_Common(tableName in varchar2) is v_sql VARCHAR2(20000); --????SQL pk_col_name VARCHAR2(800); --主键SQL insert_col_name_A VARCHAR2(20000); --A表字段 insert_col_name_

iOS网络编程同步GET方法请求编程

iOS SDK为HTTP请求提供了同步和异步请求两种不同的API,而且可以使用GET或POST等请求方法.我们先了解其中最为简单的同步GET方法请求. 首先实现查询业务,查询业务请求可以在主视图控制器MasterViewController类中实现,其中MasterViewController.h代码如下: Java代码   #import <UIKit/UIKit.h> #import “NSString+URLEncoding.h” #import “NSNumber+Message.h”

java实现同步的方法

为何要实现同步 java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),      将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,      从而保证了该变量的唯一性和准确性. 一. 实例 举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块.假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.但哪个余额对应

JS-Promise(使异步操作同步执行)

Promise - JavaScript | MDN Promise - 廖雪峰的官方网站 使用Promise使异步操作同步执行非常方便,我在遇到了使不确定个数个异步操作同步执行时学习了很久这个Promise的使用(当时因为没有理解透彻也纠结了好久),并进行总结,希望可以帮到大家 单个异步操作同步 <div id="box"></div> <script> var box = document.querySelector('#box') var p

H3C设备NTP时钟无法同步排查方法

NTP 时钟无法同步排查方法:1. V5设备NTP同步的版本默认是V3版本,V7设备默认是V4版本,如果直接在V7设备上配置NTP,NTP上的时钟可能无法实现同步的,需要手动将版本更改为V3就可以同步时钟了V5设备查看版本如下:[H3C]dis ntp sessions ver[H3C]dis ntp sessions verbose clock source: 10.1.12.1clock stratum: 2clock status: configured, master, sane, va