《Go 语言并发之道》读书笔记(四)

《Go 语言并发之道》读书笔记(四)

今天这篇笔记我们记录sync包下面的Cond,Once和Pool

Cond

cond就是条件,当条件不满足的时候等待Wait(),条件满足后,继续执行。 通过Signal()和Broadcast()来通知wait结束,继续执行。我们先来看一个Signal通知的例子

func main() {

	c := sync.NewCond(&sync.Mutex{})
	queue := make([]interface{}, 0, 10)

	removeFromQueue := func(delay time.Duration) {
		time.Sleep(delay)
		c.L.Lock()
		queue = queue[1:]
		fmt.Println("Removed from queue")
		c.L.Unlock()

		c.Signal()
	}

	for i := 0; i < 10; i++ {
		c.L.Lock()
		if len(queue) == 2 {
			c.Wait()
		}

		fmt.Println("adding to the queue")
		queue = append(queue, struct{}{})
		go removeFromQueue(1 * time.Second)
		c.L.Unlock()

	}

	fmt.Printf("queue length %d", len(queue))
}

这个程序是初始化了一个队列, 当队列的长度是2的时候,主goroutine等待, remove goruntine会删除队列中的数据,然后通过Signal方法通知主goroutine结束等待,继续执行添加。
这个程序执行的效果是这样的

adding to the queue
adding to the queue
Removed from queue
adding to the queue
Removed from queue
adding to the queue
Removed from queue
adding to the queue
Removed from queue
adding to the queue
Removed from queue
adding to the queue
Removed from queue
adding to the queue
Removed from queue
adding to the queue
Removed from queue
adding to the queue

可以看到当添加了两个后,就等待了,后面remove一个就添加一个,最后还有两个还没有来得及remove,主goroutine就退出了。

如果把c.Signal去掉,那么就会报死锁的错误, 因为主的goroutine等待了,子的gorountine也执行完了,就是都asleep了,就导致了报错。

adding to the queue
adding to the queue
Removed from queue
Removed from queue
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [sync.Cond.Wait]:
sync.runtime_notifyListWait(0xc000064050, 0x0)
        C:/Program Files/Go/src/runtime/sema.go:517 +0x152
sync.(*Cond).Wait(0xeded38?)
        C:/Program Files/Go/src/sync/cond.go:70 +0x8c
main.main()
        C:/Learn/go/helloworld/goroutinelearn/class4/main.go:30 +0x331
exit status 2

如果仅仅是通知一个等待一个,通过channel就可以做到。 如果要通知多个goroutine,那么就需要用到Broadcast. 我们把上面的例子稍微改动一下,让多个删除的goroutine等待插入goroutine的通知,代码如下所示


	c := sync.NewCond(&sync.Mutex{})
	queue := make([]interface{}, 0, 10)
	var waitgroup sync.WaitGroup

	removeFromQueue := func(delay time.Duration) {
		c.L.Lock()
		for len(queue) < 1 {
			c.Wait()
		}
		queue = queue[1:]
		fmt.Println("Removed from queue")
		waitgroup.Done()
		c.L.Unlock()

	}

	for i := 0; i < 2; i++ {
		c.L.Lock()

		go removeFromQueue(1 * time.Second)
		go removeFromQueue(1 * time.Second)

		c.L.Unlock()

	}

	waitgroup.Add(4)

	for i := 0; i < 4; i++ {
		c.L.Lock()

		fmt.Println("adding to the queue")
		queue = append(queue, struct{}{})

		c.Broadcast()

		c.L.Unlock()

	}

	waitgroup.Wait()

我们故意做成一次循环调用两个删除goroutine, 然后在删除里面当queue的数量为空的时候等待,
插入的时候,通过Broadcast来广播这个消息。程序运行结果

adding to the queue
adding to the queue
adding to the queue
Removed from queue
Removed from queue
Removed from queue
adding to the queue
Removed from queue

结果可以看出,添加了之后,随后就能删除掉。 通知多个等待的goroutine,Broadcast还是比较有用。

Once

once 我们顾名思意就是一次, 只运行一次的意思。 这种对于只需要执行一次的功能会非常有用。
看下面的示例代码

	var count int
	var lock sync.RWMutex

	increment := func() {
		lock.Lock()
		count++
		lock.Unlock()
	}

	decrement := func() {
		lock.Lock()
		fmt.Printf(" call decrement 
")
		count--
		lock.Unlock()
	}

	var once sync.Once

	var increments sync.WaitGroup
	increments.Add(100)
	for i := 0; i < 100; i++ {
		go func() {
			defer increments.Done()
			//increment()
			once.Do(increment)
		}()
	}

	once.Do(decrement)

	increments.Wait()

	fmt.Printf("Count is %d 
", count)


代码的输出为“Count is 1”, 我们通过once.Do 调用了increment 100次, 调用了 decrement 1次,但是实际上increment只被调用了一次。 once.Do 是保证它只被调用一次,不是细到方法,不是说一个方法调用一次,而是所有的都只调用一次。

Pool

谈到池,我们想到最多的就是线程池或者数据库连接池, 也比较好理解,就是创建一个资源比较耗时的时候,我们通过池来缓存一些资源,这样就不用每次都创建。
看下面示例代码


	connPool := warmServiceConnCache()
	connPool.Put(connPool.New())
	connPool.Put(connPool.New())

	for i := 1; i < 10; i++ {
		conn1 := connPool.Get().(*Conncetor)
		conn1.connect()
		connPool.Put(conn1)
	}
}

func warmServiceConnCache() *sync.Pool {
	p := &sync.Pool{
		New: connectToService,
	}

	return p
}

type Conncetor struct {
}

func (connector *Conncetor) connect() {
	fmt.Println(" connecting")
}

func connectToService() interface{} {
	fmt.Println(" creating new instance")
	return new(Conncetor)
}

我们通过warmServiceConnCache 来返回一个Pool, 然后往这个Pool 中放入两个Connector,
通过Pool.Get()方法拿到创建的Connector, 调用了10次, 都是从Pool里面拿对象,而不需要创建10次,节省了资源。
程序的输出结果如下

 creating new instance
 creating new instance
 connecting
 connecting
 connecting
 connecting
 connecting
 connecting
 connecting
 connecting
 connecting

总结

这三个类,对于写并发程序还是很有作用,光看不是很理解怎么使用,通过敲代码,修改代码,能够对他们的用法有比较清晰的理解。

hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 《Go 语言并发之道》读书笔记(四)