首页 > 其他分享 >《Go 语言并发之道》读书笔记(四)

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

时间:2022-11-22 15:59:13浏览次数:48  
标签:adding 读书笔记 goroutine sync queue 并发 connecting Go Removed

今天这篇笔记我们记录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 \n")
		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 \n", 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

总结

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

标签:adding,读书笔记,goroutine,sync,queue,并发,connecting,Go,Removed
From: https://www.cnblogs.com/dk168/p/16915333.html

相关文章

  • mongodb
    mongodb文档、非关系型数据数据库,底层索引结构使用的是B-树,只要找到它的子索引就可以进行访问,单次查询在结构上看是优于mysql,因为mysql是B+树(1)B+树相邻接点的指针可以......
  • Crony 一个基于Go语言实现的分布式定时任务管理平台
    crony-分布式定时任务管理平台1.基本介绍1.1项目背景项目中存在许多定时任务,很多代码写法都是采取见缝插针式的写法或者直接丢到task服务里面写,存在以下问......
  • golang
    golang  TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRomanianChineseSimplifiedHungarian......
  • 笑死!Go语言侵犯人权!
    2022年6月,编程语言的七国集团会议在风景优美的Linux庄园如期召开。 病毒肆虐,各国首脑也都保持了良好的社交距离。  C语言作为会议召集人,在大屏幕上用一幅图总结了......
  • django内置分页
    一.普通分页参考:https://blog.csdn.net/qq_37605109/article/details/1245140371.views.py中fromdjango.core.paginatorimportPaginatordefauthor_list(request......
  • Dijkstra Algorithm
    与BFS不同的是每条路径多了权重1.步骤:找到最便宜的节点,即可在最短时间内前往的节点对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销。重复这个过程,直到对......
  • 并发、进程、线程
    并发并发:一个程序同时执行多个独立任务。并发假象:单CPU,上下文切换;多CPU,真正并行功能:提高性能。进程进程:可执行程序运行起来即创建一个进程线程线程:代码的执行通路,每个进程......
  • 读书笔记·深入解析CSS·第二部分
    浮动设计初衷浮动能将一个元素拉到容器的一侧,这样文档流就能包围它。双容器模式用于将内容居中。通过将内容放在两个嵌套的容器中,然后给内层的容器设置外边距,让它在外......
  • golang如何使用go test?
    ​​gotest-run=TestHelloworld​​​​gotest-bench=BenchmarkHelloworld​​​​gotest-timeout30scmap-run^(TestMap)$​​​​test-benchmem-run=^$cmap-......
  • Golang编译缓存与实际项目不一致时报错has no field or method
    go1.9不明确是不是只在这个版本有小bug,有时候对最新进度的项目编译,会报上一个版本的错误,甚至改动无法编译,报出类似下面的错误:util\common\tools.go:2217:19:dtp.ClearWhe......