单例模式,是一种常用的软件设计模式,在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
package main
import (
"fmt"
"sync"
"time"
)
var once sync.Once
func main() {
for i := 0; i < 10; i++ {
once.Do(onces)
fmt.Println("1", "---", i)
}
for i := 0; i < 10; i++ {
go func(i int) {
once.Do(onced)
fmt.Println("2", "---", i)
}(i)
}
time.Sleep(time.Second*5)
}
func onces() {
fmt.Println("onces")
}
func onced() {
fmt.Println("onced")
}
打印结果为:
onces
1 --- 0
1 --- 1
1 --- 2
1 --- 3
1 --- 4
1 --- 5
1 --- 6
1 --- 7
1 --- 8
1 --- 9
2 --- 0
2 --- 1
2 --- 8
2 --- 2
2 --- 4
2 --- 5
2 --- 7
2 --- 6
2 --- 9
2 --- 3
可见,整个程序,只会执行onces()方法一次,onced()方法是不会被执行的。
为了等待go程,我们都用过哪些方法呢?
package main
import (
"fmt"
"time"
)
func main(){
for i := 0; i < 100 ; i++{
go fmt.Println(i)
}
time.Sleep(time.Second)
}
主线程为了等待goroutine都运行完毕,不得不在程序的末尾使用time.Sleep()
来睡眠一段时间,等待其他线程充分运行。但是对于实际生活的大多数场景来说,大部分时候我们都无法预知for循环内代码运行时间的长短。这时候就不能使用time.Sleep()
来完成等待操作了。
func main() {
c := make(chan bool, 100)
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
c <- true
}(i)
}
for i := 0; i < 100; i++ {
<-c
}
}
首先可以肯定的是使用管道是能达到我们的目的的,而且不但能达到目的,还能十分完美的达到目的。但是假设我们有一万、十万甚至更多的for循环,也要申请同样数量大小的管道出来,对内存也是不小的开销。
这时,sync.WaitGroup
就能帮我们更加方便的达到目的了。
WaitGroup
对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait()
用来控制计数器的数量。Add(n)
把计数器设置为n
,Done()
每次把计数器-1
,wait()
会阻塞代码的运行,直到计数器地值减为0。
func main() {
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}
这里首先把wg
计数设置为100, 每个for循环运行完毕都把计数器减一,主函数中使用Wait()
一直阻塞,直到wg为零——也就是所有的100个for循环都运行完毕。
另一个例子:
var wg sync.WaitGroup // 创建同步等待组对象
func main() {
//设置等待组中,要执行的goroutine的数量
wg.Add(1)
go fun1()
go fun2()
fmt.Println("main阻塞,等待wg中的子goroutine结束。。")
wg.Wait() //表示main goroutine进入等待,意味着阻塞
fmt.Println("main解除阻塞。。")
}
func fun1() {
for i:=1;i<=100;i++{
fmt.Println("fun5.。。i:",i)
}
wg.Done() //给wg等待中的执行的goroutine数量减1.同Add(-1)
}
func fun2() {
defer wg.Done()
for j:=1;j<=100;j++{
fmt.Println("\tfun6..j,",j)
}
}
当wg.Add(2)
时打印结果:
main进入阻塞状态。。。等待wg中的子goroutine结束。。
fun1.。。i: 1
fun1.。。i: 2
fun1.。。i: 3
fun1.。。i: 4
fun1.。。i: 5
fun1.。。i: 6
fun1.。。i: 7
fun1.。。i: 8
fun1.。。i: 9
fun1.。。i: 10
fun2..j, 1
fun2..j, 2
fun2..j, 3
fun2..j, 4
fun2..j, 5
fun2..j, 6
fun2..j, 7
fun2..j, 8
fun2..j, 9
fun2..j, 10
main,解除阻塞。。
这时主go程会等待两个子go程结束才会解除阻塞
当wg.Add(1)
时打印结果:
main进入阻塞状态。。。等待wg中的子goroutine结束。。
fun1.。。i: 1
fun1.。。i: 2
fun1.。。i: 3
fun1.。。i: 4
fun1.。。i: 5
fun1.。。i: 6
fun1.。。i: 7
fun1.。。i: 8
fun1.。。i: 9
fun2..j, 1
fun2..j, 2
fun2..j, 3
fun2..j, 4
fun2..j, 5
fun2..j, 6
fun2..j, 7
fun2..j, 8
fun2..j, 9
fun2..j, 10
main,解除阻塞。。
这时主go程只会等待一个子go程结束就会解除阻塞
此时还有可能
main进入阻塞状态。。。等待wg中的子goroutine结束。。
fun2..j, 1
fun2..j, 2
fun2..j, 3
fun2..j, 4
fun2..j, 5
fun2..j, 6
fun2..j, 7
fun2..j, 8
fun1.。。i: 1
fun1.。。i: 2
fun1.。。i: 3
fun1.。。i: 4
fun1.。。i: 5
fun1.。。i: 6
fun1.。。i: 7
fun1.。。i: 8
fun1.。。i: 9
fun1.。。i: 10
fun2..j, 9
fun2..j, 10
main,解除阻塞。。
panic: sync: negative WaitGroup counter
为什么会这样呢?
因为此时计数器变成了负数,就会panic
所以:
我们不能使用Add()
给wg
设置一个负值,否则代码将会报错,同样使用Done()
也要特别注意不要把计数器设置成负数了。
还应注意:
WaitGroup对象不是一个引用类型,在通过函数传值的时候需要使用地址:
func main() {
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go f(i, &wg)
}
wg.Wait()
}
// 一定要通过指针传值,不然进程会进入死锁状态
func f(i int, wg *sync.WaitGroup) {
fmt.Println(i)
wg.Done()
}