◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。
SEM推广过程中需要规避的低级错误
作者:曦曦SEO时间:2022-12-11 17:13:09来源:成都seo浏览:33
本文主要讲述【SEM推广过程中需要规避的低级错误】的相关内容,希望能对各位有所帮助。
本文导读目录:
1、Go中看似简单的WaitGroup源码设计,竟然暗含这么多知识?
2、做百度竞价为什么要找托管?避免最低级错误
Go中看似简单的WaitGroup源码设计,竟然暗含这么多知识?
Go语言提供的协程goroutine可以让我们很容易地写出多线程程序,但是,如何让这些并发执行的goroutine得到有效地控制,这是我们需要探讨的问题。正如小菜刀在[《Golang并发控制简述》][Golang]中所述,Go标准库为我们提供的同步原语中,锁与原子操作注重控制goroutine之间的数据安全,WaitGroup、channel与Context控制的是它们的并发行为。关于[锁][Link 1]、[原子操作][Link 2]、[channel][]?的实现原理小菜刀均有详细地解析过。因此本文,我们将重点放在WaitGroup上。
**初识WaitGroup**
WaitGroup是sync包下的内容,用于控制协程间的同步。WaitGroup使用场景同名字的含义一样,当我们需要等待一组协程都执行完成以后,才能做后续的处理时,就可以考虑使用。
1func?main()?{
2var?wg?sync.WaitGroup
3
4wg.Add(2)?//worker?number?2
5
6go?func()?{
7//?worker?1?do?something
8fmt.Println("goroutine 1 done!")
9wg.Done()
10}()
11
12go?func()?{
13//?worker?2?do?something
14fmt.Println("goroutine 2 done!")
15wg.Done()
16}()
17
18wg.Wait()?//?wait?all?waiter?done
19fmt.Println("all work done!")
20}
21
22//?output
23goroutine?2?done!
24goroutine?1?done!
25all work done!
可以看到WaitGroup的使用非常简单,它提供了三个方法。虽然goroutine之间并不存在类似于父子关系,但是为了方便理解,本文会将调用Wait函数的goroutine称为主goroutine,调用Done函数的goroutine称呼为子goroutine。
1func?(wg?*WaitGroup)?Add(delta?int)//?增加WaitGroup中的子goroutine计数值
2func?(wg?*WaitGroup)?Done()//?当子goroutine任务完成,将计数值减1
3func?(wg?*WaitGroup)?Wait()//?阻塞调用此方法的goroutine,直到计数值为0
4
那么它是如何实现的呢?在源码`src/sync/waitgroup.go`中,我们可以看到它的核心源码只有100行不到,十分地精练,非常值得学习。
**前置知识**
代码少,不代表就实现简单,易于理解。相反,如果读者没有下述中的前置知识,想要真正理解WaitGroup的实现是会比较费力的。在解析源码之前,我们先过一遍这些知识(如果你都已经掌握,那就可以直接跳到后文的源码解析部分)。
##### 信号量 #####
在学习操作系统时,我们知道信号量是一种保护共享资源的机制,用于解决多线程同步问题。信号量`s`是具有非负整数值的全局变量,只能由两种特殊的操作来处理,这两种操作称为`P`和`V`。
* `P(s)`:如果`s`是非零的,那么`P`将`s`减1,并且立即返回。如果`s`为零,那么就挂起这个线程,直到`s`变为非零,等到另一个执行`V(s)`操作的线程唤醒该线程。在唤醒之后,`P`操作将`s`减1,并将控制返回给调用者。
* `V(s)`:`V`操作将`s`加1。如果有任何线程阻塞在`P`操作等待`s`变为非零,那么`V`操作会唤醒这些线程中的一个,然后该线程将`s`减1,完成它的`P`操作。
在Go的底层信号量函数中
* `runtime_Semacquire(s *uint32)` 函数会阻塞goroutine直到信号量`s`的值大于0,然后原子性地减这个值,即`P`操作。
* `runtime_Semrelease(s *uint32, lifo bool, skipframes int)` 函数原子性增加信号量的值,然后通知被`runtime_Semacquire`阻塞的goroutine,即`V`操作。
这两个信号量函数不止在WaitGroup中会用上,在[《Go精妙的互斥锁设计》][Link 1]一文中,我们发现Go在设计互斥锁的时候也少不了信号量的参与。
##### 内存对齐 #####
对于以下的结构体,你能回答出它占用的内存是多少吗
1type?Ins?struct?{
2x?bool//?1个字节
3y?int32?//?4个字节
4z?byte//?1个字节
5}
6
7func?main()?{
8ins?:=?Ins{}
9fmt.Printf("ins?size:?%d,?align:?%d\n",?unsafe.Sizeof(ins),?unsafe.Alignof(ins))
10}
11
12//output
13ins?size:?12,?align:?4
按照结构体中字段的大小而言,`ins`对象占用内存应该是 1+4+1=6 个字节,但是实际上确实12个字节,这就是内存对齐所致。从[《CPU缓存体系对Go程序的影响》][CPU_Go]一文中,我们知道CPU的内存读取并不是一个字节一个字节地读取的,而是一块一块的。因此,在类型的值在内存中对齐的情况下,计算机的加载或者写入会很高效。
在聚合类型(结构体或数组)的内存所占长度或许会比它元素所占内存之和更大。编译器会添加未使用的内存地址用于填充内存空隙,以确保连续的成员或元素相当于结构体或数组的起始地址是对齐的。
![16ec3184dfd41bfe.png][]
因此,在我们设计结构体时,当结构体成员的类型不同时,将相同类型的成员定义在相邻位置可以更节省内存空间。
##### 原子操作CAS #####
CAS是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而**避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题**。该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。关于Go中原子操作的底层实现,小菜刀在[《同步原语的基石》][Link 2]一文中有详细介绍。
##### 移位运算 >> 与
文章标题:SEM推广过程中需要规避的低级错误
文章链接:http://www.snjkrh.cn/3364.html
下一篇:SEM竞价有何优势可言?