深圳做网站公司那家比较好,织梦cms 网站栏目管理,设计制作小车的基本步骤是,如何建设网站教程1.锁有时候我们的代码中可能会存在多个 goroutine 同时操作一个资源#xff08;临界区#xff09;的情况#xff0c;这种情况下就会发生竞态问题#xff08;数据竞态#xff09;。(1)、互斥锁#xff1b;(2)、读写互斥锁#xff1b;(3)、sync.WaitGroup#xff1b;(4)、…1.锁 有时候我们的代码中可能会存在多个 goroutine 同时操作一个资源临界区的情况这种情况下就会发生竞态问题数据竞态。 (1)、互斥锁(2)、读写互斥锁(3)、sync.WaitGroup(4)、sync.Once(5)、sync.Map(6)、atomic包
var (x int64m sync.Mutex // 互斥锁rwMutex sync.RWMutexmutex sync.Mutex
)func Mutex() {wg.Add(2)go add()go add()wg.Wait()fmt.Println(x)
}func add() {for i : 0; i 5000; i {m.Lock() // 修改x前加锁x x 1m.Unlock() // 改完解锁}wg.Done()
}func RWMutex() {/*读写锁分为两种读锁和写锁。当一个 goroutine 获取到读锁之后其他的 goroutine 如果是获取读锁会继续获得锁如果是获取写锁就会等待而当一个 goroutine 获取写锁之后其他的 goroutine 无论是获取读锁还是写锁都会等待。*/// 使用互斥锁10并发写1000并发读do(writeWithLock, readWithLock, 10, 1000) // x:10 cost:1.466500951s// 使用读写互斥锁10并发写1000并发读do(writeWithRWLock, readWithRWLock, 10, 1000) // x:10 cost:117.207592ms/*从最终的执行结果可以看出使用读写互斥锁在读多写少的场景下能够极大地提高程序的性能。不过需要注意的是如果一个程序中的读操作和写操作数量级差别不大那么读写互斥锁的优势就发挥不出来。*/
}// writeWithLock 使用互斥锁的写操作
func writeWithLock() {mutex.Lock() // 加互斥锁x x 1time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒mutex.Unlock() // 解互斥锁wg.Done()
}// readWithLock 使用互斥锁的读操作
func readWithLock() {mutex.Lock() // 加互斥锁time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒mutex.Unlock() // 释放互斥锁wg.Done()
}// writeWithLock 使用读写互斥锁的写操作
func writeWithRWLock() {rwMutex.Lock() // 加写锁x x 1time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒rwMutex.Unlock() // 释放写锁wg.Done()
}// readWithRWLock 使用读写互斥锁的读操作
func readWithRWLock() {rwMutex.RLock() // 加读锁time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒rwMutex.RUnlock() // 释放读锁wg.Done()
}func do(wf, rf func(), wc, rc int) {start : time.Now()// wc个并发写操作for i : 0; i wc; i {wg.Add(1)go wf()}// rc个并发读操作for i : 0; i rc; i {wg.Add(1)go rf()}wg.Wait()cost : time.Since(start)fmt.Printf(x:%v cost:%v\n, x, cost)
}func WaitGroup() {/*WaitGroup在代码中生硬的使用time.Sleep肯定是不合适的Go语言中可以使用sync.WaitGroup来实现并发任务的同步。 sync.WaitGroup有以下几个方法方法名 功能func (wg * WaitGroup) Add(delta int) 计数器delta(wg *WaitGroup) Done() 计数器-1(wg *WaitGroup) Wait() 阻塞直到计数器变为0sync.WaitGroup内部维护着一个计数器计数器的值可以增加和减少。例如当我们启动了 N 个并发任务时就将计数器值增加N。每个任务完成时通过调用 Done 方法将计数器减1。通过调用 Wait 来等待并发任务执行完当计数器值为 0 时表示所有并发任务已经完成。*/wg.Add(1)go hello()wg.Wait()fmt.Println(ni hao ya !!!)
}
func hello() {defer wg.Done()fmt.Println(hello world....)
}2.SyncOnce 在某些场景下我们需要确保某些操作即使在高并发的场景下也只会被执行一次例如只加载一次配置文件等。Go语言中的sync包中提供了一个针对只执行一次场景的解决方案——sync.Oncesync.Once只有一个Do方法延迟一个开销很大的初始化操作到真正用到它的时候再执行是一个很好的实践。因为预先初始化一个变量比如在init函数中完成初始化会增加程序的启动耗时而且有可能实际执行过程中这个变量没有用上那么这个初始化操作就不是必须要做的。
func SyncOnce() {GetInstance() //并发安全的单例模式
}type singleton struct{}var instance *singleton
var once sync.Oncefunc GetInstance() *singleton {once.Do(func() {instance singleton{}})return instance
}3.并发安全的map sync.Map 是 Go 语言中提供的一个并发安全的 map 类型它是 Go 语言中 map 的替代品它是并发安全的并且它是通过引入 sync.RWMutex 来实现的。在 Go 语言中map 是无序的而 sync.Map 是基于 sync.RWMutex 实现的所以它是并发安全的。sync.Map 的底层实现是通过哈希表来实现的哈希表的底层实现是通过数组链表来实现的所以它是通过数组链表来实现的所以它是通过数组链表来实现的所以它是通过数组链表来实现的所以它是通过数组链表来实现的所以它是通过数组链表来实现的所以它是通过数组链表来实现的所以它是通过数组链表来实现的所以它是通过数组链表来实现的所以它是通过数组链表来。var smp sync.Map{}func SyncMap() {wg : sync.WaitGroup{}// 对m执行20个并发的读写操作for i : 0; i 20; i {wg.Add(1)go func(n int) {key : strconv.Itoa(n)smp.Store(key, n) // 存储key-valuevalue, _ : smp.Load(key) // 根据key取值fmt.Printf(k:%v,v:%v\n, key, value)wg.Done()}(i)}wg.Wait()
}4.atomic atomic包提供了底层的原子级内存操作对于同步算法的实现很有用。这些函数必须谨慎地保证正确使用。除了某些特殊的底层应用使用通道或者 sync 包的函数/类型实现同步更好。针对整数数据类型int32、uint32、int64、uint64我们还可以使用原子操作来保证并发安全通常直接使用原子操作比使用锁操作效率更高。Go语言中原子操作由内置的标准库sync/atomic提供func (o *TrinoQueryRunningExporter) AcquireLock() bool {return atomic.CompareAndSwapInt64(o.Lock, 0, 1)}func (o *TrinoQueryRunningExporter) ReleaseLock() {atomic.CompareAndSwapInt64(o.Lock, 1, 0)}
这里的锁就是原子操作的使用案例。
func Atomic() {c1 : CommonCounter{} // 非并发安全test(c1)c2 : MutexCounter{} // 使用互斥锁实现并发安全test(c2)c3 : AtomicCounter{} // 并发安全且比互斥锁效率更高test(c3)
}type Counter interface {Inc()Load() int64
}// 普通版
type CommonCounter struct {counter int64
}func (c CommonCounter) Inc() {c.counter
}func (c CommonCounter) Load() int64 {return c.counter
}// 互斥锁版
type MutexCounter struct {counter int64lock sync.Mutex
}func (m *MutexCounter) Inc() {m.lock.Lock()defer m.lock.Unlock()m.counter
}func (m *MutexCounter) Load() int64 {m.lock.Lock()defer m.lock.Unlock()return m.counter
}// 原子操作版
type AtomicCounter struct {counter int64
}func (a *AtomicCounter) Inc() {atomic.AddInt64(a.counter, 1)
}func (a *AtomicCounter) Load() int64 {return atomic.LoadInt64(a.counter)
}func test(c Counter) {var wg sync.WaitGroupstart : time.Now()for i : 0; i 1000; i {wg.Add(1)go func() {c.Inc()wg.Done()}()}wg.Wait()end : time.Now()fmt.Println(c.Load(), end.Sub(start))
}5.error Go 语言中把错误当成一种特殊的值来处理不支持其他语言中使用try/catch捕获异常的方式Go 语言中使用一个名为 error 接口来表示错误类型。error 接口只包含一个方法——Error这个函数需要返回一个描述错误信息的字符串。当一个函数或方法需要返回错误时我们通常是把错误作为最后一个返回值。我们可以根据需求自定义 error最简单的方式是使用errors 包提供的New函数创建一个错误。errors.New当我们需要传入格式化的错误描述信息时使用fmt.Errorf是个更好的选择。func ErrorNew() {id : -1var err errorif id 0 {err errors.New(无效的id)fmt.Printf(id error:%v \n, err)}fmt.Println(fmt.Errorf(查询数据库失败v err:%v \n, err))//但是上面的方式会丢失原有的错误类型只拿到错误描述的文本信息。//为了不丢失函数调用的错误链使用fmt.Errorf时搭配使用特殊的格式化动词%w可以实现基于已有的错误再包装得到一个新的错误。fmt.Println(fmt.Errorf(查询数据库失败w err:%w \n, err))//自定义结构体类型可以自己定义结构体类型实现 error接口oper : OpError{Op: update,}fmt.Println(oper.Error())
}// Error OpError 类型实现error接口
func (e *OpError) Error() string {return fmt.Sprintf(无权执行%s操作, e.Op)
}// OpError 自定义结构体类型
type OpError struct {Op string
}6.类型转换 strconv包实现了基本数据类型与其字符串表示的转换主要有以下常用函数 Atoi()、Itoa()、parse系列、format系列、append系列。【扩展阅读】这是C语言遗留下的典故。C语言中没有string类型而是用字符数组(array)表示字符串所以Itoa对很多C系的程序员很好理解。strconv.Atoi strconv.ItoaParse类函数用于转换字符串为给定类型的值ParseBool()、ParseFloat()、ParseInt()、 ParseUint()。Format系列函数实现了将给定类型数据格式化为string类型数据的功能
func TypeTransfer() {s1 : 100i1, err : strconv.Atoi(s1) // Atoi()函数用于将字符串类型的整数转换为int类型if err ! nil {fmt.Println(cant convert to int)} else {fmt.Printf(type:%T value:%#v\n, i1, i1) //type:int value:100}v : int64(-42)s10 : strconv.FormatInt(v, 10)fmt.Printf(%T, %v\n, s10, s10)s16 : strconv.FormatInt(v, 16)fmt.Printf(%T, %v\n, s16, s16)
}7.ini 在Go语言中init()函数是一种特殊的函数用于在程序启动时自动执行一次。它的存在为我们提供了一种机制可以在程序启动时进行一些必要的初始化操作为程序的正常运行做好准备
go语言中init函数用于包(package)的初始化该函数是go语言的一个重要特性。(1) init函数是用于程序执行前做包的初始化的函数比如初始化包里的变量等(2) 每个包可以拥有多个init函数(3) 包的每个源文件也可以拥有多个init函数(4) 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)(5) 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序(6) init函数不能被其他函数调用而是在main函数执行之前自动被调用
init函数和main函数的异同:
相同点两个函数在定义时不能有任何的参数和返回值且Go程序自动调用。
不同点init可以应用于任意包中且可以重复定义多个。main函数只能用于main包中且只能定义一个。
go中包的初始化顺序:
首先初始化包内声明的变量
之后调用 init 函数
最后调用 main 函数
两个函数的执行顺序
对同一个go文件的init()调用顺序是从上到下的。
对同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数。
对于不同的package如果不相互依赖的话按照main包中先import的后调用的顺序调用其包中的init()如果package存在依赖则先调用最早被依赖的package中的init()最后调用main函数。
如果init函数中使用了println()或者print()你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用对于正式环境不要使用。
https://www.cnblogs.com/chenjiazhan/p/17473207.html
https://www.cnblogs.com/XiaoXiaoShuai-/p/14642055.html
init 函数的用途
(1) 初始化全局变量
(2) 执行一些必要的验证操作
注意
init 函数不能被显式调用
init 函数只执行一次
避免在 init 函数中执行耗时操作func Init() {fmt.Println(Init Test)
}func init() {fmt.Println(hello world)
}
8.test Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的并不需要学习新的语法、规则或工具。go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内所有以_test.go为后缀名的源代码文件都是go test测试的一部分不会被go build编译到最终的可执行文件中。
在*_test.go文件中有三种类型的函数单元测试函数、基准测试函数和示例函数。类型 格式 作用测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确基准函数 函数名前缀为Benchmark 测试函数的性能示例函数 函数名前缀为Example 为文档提供示例文档go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数然后生成一个临时的main包用于调用相应的测试函数然后构建并运行、报告测试结果最后清理测试中生成的临时文件。
func TestChannel() {/*第一次循环时 i 1select 语句中包含两个 case 分支此时由于通道中没有值可以接收所以x : -ch 这个 case 分支不满足而ch - i这个分支可以执行会把1发送到通道中结束本次 for 循环第二次 for 循环时i 2由于通道缓冲区已满所以ch - i这个分支不满足而x : -ch这个分支可以执行从通道接收值1并赋值给变量 x 所以会在终端打印出 1后续的 for 循环以此类推会依次打印出3、5、7、9。*/ch : make(chan int, 1)for i : 1; i 10; i {select {case x : -ch:fmt.Println(x)case ch - i:}}
}
9.指针 Go语言中的指针不能进行偏移和运算因此Go语言中的指针操作非常简单我们只需要记住两个符号取地址和*根据地址取值。func Pointer() {a : 100b : afmt.Printf(a:%v, b:%v,bp:%p \n, a, b, b)fmt.Printf(b:%v,\n, b)modValue(b)fmt.Printf(a:%v, b:%v,bp:%p \n, a, b, b)
}func modValue(i *int) {if i ! nil {*i 101}
}10.contextfunc Context() {wg.Add(2)go func() {time.Sleep(time.Second * 2)fmt.Println(job 1 done)wg.Done()}()go func() {time.Sleep(time.Second * 1)fmt.Println(job 2 done)wg.Done()}()wg.Wait()fmt.Println(all job done)
}func Context2() {stop : make(chan bool)go func() {for {select {case -stop:fmt.Println(got the stop channel)returndefault:fmt.Println(still working)time.Sleep(time.Second * 1)}}}()time.Sleep(time.Second * 5)fmt.Println(stop the goroutine)stop - truetime.Sleep(time.Second * 5)
}func Context3() {ctx, cancel : context.WithCancel(context.Background())go worker(ctx, worker1)go worker(ctx, worker2)go worker(ctx, worker3)time.Sleep(time.Second * 5)fmt.Println(stop the goroutine)cancel()time.Sleep(time.Second * 5)
}func worker(ctx context.Context, name string) {go func() {for {select {case -ctx.Done():fmt.Println(got the stop channel)returndefault:fmt.Println(name, still working)time.Sleep(time.Second * 1)}}}()
}func Context4() {// 创建一个带有取消功能的上下文ctx, cancel : context.WithCancel(context.Background())defer cancel()// 设置一个截止时间为5秒后ctx, cancel context.WithDeadline(ctx, time.Now().Add(5*time.Second))defer cancel()// 向上下文中添加一个值ctx context.WithValue(ctx, key, value)// 启动一个goroutine来监听上下文的取消信号go func() {select {case -ctx.Done():fmt.Println(Context done:, ctx.Err())}}()// 启动一个goroutine来获取上下文中的值go func() {time.Sleep(2 * time.Second)value : ctx.Value(key)fmt.Println(Context value:, value)}()// 模拟一个耗时操作select {case -time.After(10 * time.Second):fmt.Println(Operation completed)case -ctx.Done():fmt.Println(Operation canceled due to context)}// 获取上下文的截止时间if deadline, ok : ctx.Deadline(); ok {fmt.Println(Context deadline:, deadline)} else {fmt.Println(No deadline set for context)}
}func ContextWaitGroup() {ctx, cancel : context.WithTimeout(context.Background(), 5*time.Second)defer cancel()var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()for {select {case -ctx.Done():fmt.Println(Goroutine canceled due to context)returndefault:// 模拟一些工作fmt.Println(Goroutine working...)time.Sleep(1 * time.Second)}}}()wg.Wait()fmt.Println(Main goroutine finished)
}func ContextWaitGroup1() {ctx, cancel : context.WithTimeout(context.Background(), 2*time.Second)defer cancel()go func() {for {select {case -ctx.Done():fmt.Println(goroutine exiting...)returndefault:fmt.Println(goroutine working...)time.Sleep(500 * time.Millisecond)}}}()time.Sleep(3 * time.Second)fmt.Println(main function exiting...)
}11.闭包 Closure是一种特殊的函数它可以捕获其创建时所在作用域中的变量。闭包通常与匿名函数一起使用匿名函数可以访问并操作不在其参数列表中的外部变量。Go语言中的闭包有几个特殊的用途和优势状态封装,控制变量生命周期,函数工厂,实现回调和延后执行,模块化和封装,实现接口,高阶函数,迭代器和生成器,避免命名冲突;使用闭包的注意事项:内存泄漏,并发安全,循环引用.
func Closure() {/*这里这里start : time.Now() 已经运行了返回的函数再跟后面做减法*/defer TimeCost(test closure, 11)()time.Sleep(time.Second * 2)
}func TimeCost(handlerName string, req ...interface{}) func() {fmt.Printf(fmt.Sprintf(TimeCost for %s start now., handlerName))start : time.Now()return func() {tc : time.Since(start)fmt.Printf(fmt.Sprintf(handle %s for request:%v time cost is:%v, handlerName, req, tc))}
}12.http\UDP UDP协议User Datagram Protocol中文名称是用户数据报协议是OSIOpen System Interconnection开放式系统互联参考模型中一种无连接的传输层协议不需要建立连接就能直接进行数据发送和接收属于不可靠的、没有时序的通信但是UDP协议的实时性比较好通常用于视频直播相关领域。
func process(conn net.Conn) {defer conn.Close()for {reader : bufio.NewReader(conn)var buf [128]byten, err : reader.Read(buf[:])if err ! nil {fmt.Printf(read failed,err:%v, err)break}recv : string(buf[:n])fmt.Printf(接收到的数据%v, recv)conn.Write([]byte(ok))}
}func HttpServer() {listen, err : net.Listen(tcp, 127.0.0.1:8801)if err ! nil {fmt.Printf(listen failed,err:%v, err)return}for {conn, err : listen.Accept()if err ! nil {fmt.Printf(Accept failed ,err:%v, err)continue}go process(conn)}
}func HttpClinet() {conn, err : net.Dial(tcp, 127.0.0.1:8801)if err ! nil {fmt.Printf(connect failed,err:%v, err)return}input : bufio.NewReader(os.Stdin)for {s, _ : input.ReadString(\n)s strings.TrimSpace(s)if strings.ToUpper(s) Q {return}//给服务端发消息_, err : conn.Write([]byte(s))if err ! nil {fmt.Printf(send failed,err:%v \n, err)return}//从服务端接收消息var buf [1023]byten, err : conn.Read(buf[:])if err ! nil {fmt.Printf(read failed,err:%v \n, err)return}fmt.Printf(收到服务单回复%v, string(buf[:n]))}
}/*
RPC就是为了解决类似远程、跨内存空间、的函数/方法调用的。要实现RPC就需要解决以下三个问题。
如何确定要执行的函数调用方和被调用方都需要维护一个{ function - ID }映射表以确保调用正确的函数
如何表达参数 参数或返回值需要在传输期间序列化并转换成字节流反之亦然
如何进行网络传输只要能够完成传输调用方和被调用方就不受某个网络协议的限制
*/