网站运行速度慢的原因,wordpress弹窗登入,网站设计风格分类,普通企业网站费用【后端开发】Go语言编程实践#xff0c;Goroutines和Channels#xff0c;基于共享变量的并发#xff0c;反射与底层编程 【后端开发】Go语言高级编程#xff0c;CGO、Go汇编语言、RPC实现、Web框架实现、分布式系统 文章目录 1、并发基础, Goroutines和Channels2、基于共享…【后端开发】Go语言编程实践Goroutines和Channels基于共享变量的并发反射与底层编程 【后端开发】Go语言高级编程CGO、Go汇编语言、RPC实现、Web框架实现、分布式系统 文章目录 1、并发基础, Goroutines和Channels2、基于共享变量的并发, sync.WaitGroup和sync.Mutex3、反射与底层编程 参考资料 1、框架 go语言源码-124k, go精选框架-133k, go-gin-HTTP Web框架-80k, go-Llama 3.2框架-90k, rclone-云存储挂载-50k, go-zero-原生微服务框架-20k 2、教程 The Go programming language - 124k Go语言圣经 《The Go Programming Language》 中文版-4.5k Go语言高级编程-20k, go成长路线-6k go学习指南-3k go基础语法-菜鸟 3、应用 7天实现web-gee和数据库orm框架-16k 构建goweb应用-43k
1、并发基础, Goroutines和Channels
Goroutine 是 Go 中实现并发的基本单位。它是一种轻量级线程使用 go 关键字启动。
如果你使用过操作系统或者其它语言提供的线程那么你可以简单地把goroutine类比作一个线程这样你就可以写出一些正确的程序了。当一个程序启动时其主函数即在一个单独的goroutine中运行我们叫它main goroutine。新的goroutine会用go语句来创建。在语法上go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成。
go func() {// 这里是并发运行的代码
}()func aaa(str string){}
go aaa(aa)f() // call f(); wait for it to return
go f() // create a new goroutine that calls f(); dont waitChannels
如果说goroutine是Go语言程序的并发体的话那么channels则是它们之间的通信机制。一个channel是一个通信机制它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型也就是channels可发送数据的类型。一个可以发送int类型数据的channel一般写为chan int。和map类似channel也对应一个make创建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时我们只是拷贝了一个channel引用因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样channel的零值也是nil。
ch : make(chan int) ch - x // a send statement
x -ch // a receive expression in an assignment statement
-ch // a receive statement; result is discardedclose(ch) // 随后对基于该channel的任何发送操作都将导致panic异常。ch make(chan int) // unbuffered channel
ch make(chan int, 0) // unbuffered channel
ch make(chan int, 3) // buffered channel with capacity 3
操作不同状态的chan会引发三种行为
panic
向已经关闭的通道写数据重复关闭通道
阻塞
向未初始化的通道写/读数据向缓冲区已满的通道写入数据通道中没有数据读取该通道
非阻塞
读取已经关闭的通道这个操作会返回通道元素类型的零值可用comma, ok语法向有缓冲且没有满的通道读/写
package mainimport (testing
)func TestChanOperateStatus(t *testing.T) {t.Run(向已经关闭的通道写数据, func(t *testing.T) {ch : make(chan int)close(ch) // 关闭通道ch - 1 // 这里会引发panic因为向已关闭的通道发送数据// panic: send on closed channel [recovered]})t.Run(重复关闭通道, func(t *testing.T) {ch : make(chan int)close(ch) // 第一次关闭通道close(ch) // 再次关闭通道会引发panic// panic: close of closed channel [recovered]})t.Run(向未初始化的通道写/读数据, func(t *testing.T) {var ch chan intgo func() {ch - 1// x : -ch}()_ -ch// fatal error: all goroutines are asleep - deadlock!})t.Run(向缓冲区已满的通道写入数据, func(t *testing.T) {ch : make(chan int, 1)ch - 1 // 第一次写入缓冲区未满ch - 2 // 这里会阻塞因为缓冲区已满没有goroutine读取数据// fatal error: all goroutines are asleep - deadlock!})t.Run(通道中没有数据读取该通道, func(t *testing.T) {ch : make(chan int)_ -ch // 这里会阻塞因为没有goroutine发送数据到通道// fatal error: all goroutines are asleep - deadlock!})t.Run(读取已经关闭的通道这个操作会返回通道元素类型的零值可用comma, ok语法, func(t *testing.T) {ch : make(chan int)close(ch) // 关闭通道x, ok : -ch // x 将会是int类型的零值ok 将会是falseexpectx, expectok : 0, falseif ok ! expectok x ! expectx {t.Errorf(expect 0, false, get %d, %t\n, x, ok)}})t.Run(向有缓冲且没有满的通道写向有缓冲且不为空的通道读, func(t *testing.T) {ch : make(chan int, 2) // 1 也不会堵塞ch - 1 // 写入数据不会阻塞_ -ch // 读取数据不会阻塞})
}
2、基于共享变量的并发, sync.WaitGroup和sync.Mutex sync.WaitGroup 计数器等待并发完成 sync.Mutex 互斥锁保护共享资源 闭包捕获外部变量的值 sync.WaitGroup 是一个计数器用于等待一组 goroutine 完成。使用它的步骤如下
添加计数使用 Add(n int) 方法增加计数通常在启动 goroutine 之前调用。完成计数在 goroutine 内部使用 Done() 方法来减少计数。等待完成使用 Wait() 方法阻塞当前 goroutine直到计数器变为零。
var wg sync.WaitGroupfor i : 0; i n; i {wg.Add(1) // 增加计数go func(i int) {defer wg.Done() // 在完成时减少计数// 某些操作}(i)
}wg.Wait() // 等待所有 goroutine 完成sync.Mutex 是一种互斥锁用于保护共享资源确保同一时间只有一个 goroutine 访问它。使用方法如下
锁定使用 Lock() 方法加锁确保线程安全。解锁使用 Unlock() 方法解锁。通常推荐使用 defer 来确保在函数退出时解锁。
var mtx sync.Mutexmtx.Lock() // 上锁
// 对共享资源的访问
mtx.Unlock() // 解锁在并发操作中通过收集错误并处理它们也是很常见的做法。可以使用切片来存储可能发生的错误并且在访问这个切片时需要使用 Mutex 来保证线程安全。
var errs []error
var mtx sync.Mutexif err ! nil {mtx.Lock()errs append(errs, err)mtx.Unlock()
}闭包 在 goroutine 内部可以使用闭包来捕获外部变量的值。这对于确保在并发执行时每个 goroutine 使用到的是正确的变量非常重要。
for _, value : range values {go func(v string) {// 使用 v确保 v 是当前循环中的值}(value)
}结合以上组件可以实现并发的操作例如
var wg sync.WaitGroup
var mtx sync.Mutex
var errs []errorfor _, id : range ids {wg.Add(1)go func(id string) {defer wg.Done()// 假设这里是某个并发操作if err : doSomething(id); err ! nil {mtx.Lock()errs append(errs, err)mtx.Unlock()}}(id)
}wg.Wait()// 处理错误
if len(errs) 0 {// 处理错误逻辑
}3、反射与底层编程 反射遍历不确定的结构体的每个字段可以用反射来获取结构体的字段的值 。以及判断字段数据类型在调用适当的函数做神奇操作。 底层编程cgo 反射是由 reflect 包提供的。它定义了两个重要的类型Type 和 Value。一个 Type 表示一个Go类型。
反射是程序在运行时能够“观察”并且修改自己的行为的能力。在Go语言中反射是通过reflect包实现的它提供了两个核心功能Type和Value获取Type和Value使用reflect.TypeOf()和reflect.ValueOf()可以获取变量的动态类型和值。TypeOf返回的是Type接口而ValueOf返回的是Value接口类型和值的查询通过Type和Value接口的方法可以查询变量的类型信息和值。例如Kind()方法可以返回一个常量表示底层数据类型如Uint、Float64、Slice等修改值使用Value的Set()方法可以修改值但需要注意的是只有当值是可设置的settable时才能修改。可设置性意味着值必须通过指针传递并且使用Elem()方法获取指针指向的值进行修改动态方法调用和字段访问反射不仅可以用于基础类型和结构体还可以用于动态地调用方法和访问字段。例如可以通过MethodByName方法动态调用对象的方法
package mainimport (fmtreflect
)// 定义一个示例结构体
type Employee struct {Name stringAge intSalary float64Active bool
}// 一个通用函数使用反射打印并修改结构体字段
func inspectAndModify(v interface{}, newValue interface{}) {// 获取传入值的反射值val : reflect.ValueOf(v)// 检查是否是指针类型如果是获取其元素if val.Kind() reflect.Ptr {val val.Elem()}// 打印结构体的字段fmt.Printf(结构体类型: %s\n, val.Type())fmt.Println(字段:)for i : 0; i val.NumField(); i {field : val.Field(i)fieldType : val.Type().Field(i)// 打印字段名称和类型fmt.Printf( %s (%s): %v\n, fieldType.Name, fieldType.Type, field)// 这里我们简单示例用传入的值替换第一个可设置的字段if i 0 field.CanSet() {value : reflect.ValueOf(newValue)if value.Type().AssignableTo(field.Type()) {field.Set(value)fmt.Printf( %s字段已更新为: %v\n, fieldType.Name, field)} else {fmt.Printf( 无法将值%v赋给字段%s类型不匹配。\n, newValue, fieldType.Name)}}}
}func main() {// 创建一个 Employee 实例emp : Employee{Name: Alice, Age: 30, Salary: 65000.0, Active: true}// 使用 inspectAndModify 函数打印结构体信息并修改字段inspectAndModify(emp, Bob)// 打印修改后的结构体fmt.Println(修改后的 Employee:, emp)
}// 结构体类型: main.Employee
// 字段:
// Name (string): Alice
// Name字段已更新为: Bob
// Age (int): 30
// Salary (float64): 65000
// Active (bool): true
// 修改后的 Employee: {Bob 30 65000 true}package mainimport (fmtreflect
)type Person struct {Name stringAge intAddress string
}func main() {// 创建一个Person实例p : Person{Name: John Doe,Age: 30,Address: 123 Main St,}// 获取Person实例的反射值v : reflect.ValueOf(p)// 确保v是一个结构体if v.Kind() reflect.Struct {// 遍历结构体的所有字段for i : 0; i v.NumField(); i {field : v.Field(i)// 获取字段的名称fieldName : v.Type().Field(i).Name// 获取字段的值fieldValue : field.Interface()fmt.Printf(Field: %s, Value: %v\n, fieldName, fieldValue)}}
}Go的底层编程
Go的底层编程涉及到更接近硬件和操作系统的细节包括内存管理、指针操作等。unsafe包Go提供了unsafe包它允许程序员绕过Go的类型系统进行指针操作和内存对齐等操作。unsafe包中的Sizeof、Alignof和Offsetof可以用于获取类型的存储大小、内存对齐和字段偏移量unsafe.Pointerunsafe.Pointer是一个特殊的类型它可以存储任何类型的指针并允许进行指针转换和算术操作调用C代码通过cgoGo程序可以调用C语言代码这涉及到底层的内存管理和类型转换性能考虑底层编程和反射操作通常比直接的Go操作要慢因为它们涉及到额外的动态查询和类型转换。因此在性能敏感的应用中需要谨慎使用要编译和运行这个示例你需要确保你的Go环境可以使用cgo。通常对于大多数操作系统cgo是默认启用的。
调用c语言代码
// main.go
package main/*
#include stdio.h
#include stdlib.h// 定义转置函数
void transpose(int* src, int* dest, int rows, int cols) {for (int i 0; i rows; i) {for (int j 0; j cols; j) {dest[j * rows i] src[i * cols j];}}
}
*/
import C
import (fmtunsafe
)func main() {// 定义一个3x3的矩阵rows, cols : 3, 3src : []int{1, 2, 3, 4, 5, 6, 7, 8, 9}dest : make([]int, rows*cols)// 打印原始矩阵fmt.Println(原始矩阵:)printMatrix(src, rows, cols)// 调用C代码进行转置C.transpose((*C.int)(unsafe.Pointer(src[0])), (*C.int)(unsafe.Pointer(dest[0])), C.int(rows), C.int(cols))// 打印转置后的矩阵fmt.Println(转置后的矩阵:)printMatrix(dest, cols, rows)
}func printMatrix(matrix []int, rows, cols int) {for i : 0; i rows; i {for j : 0; j cols; j {fmt.Printf(%d , matrix[i*colsj])}fmt.Println()}
}