在 golang 中,函数指针是一种重要的类型,其主要用途包括回调函数、动态加载库函数等,因而其失效将会对程序的正确性产生严重影响。
然而,在真实的开发环境中,我们经常会遇到函数指针失效导致程序出现奇怪的错误的情况。本文将以一个具体的案例为例,分析函数指针失效的原因,并探讨如何避免这种情况的发生。
案例分析
在一个 GraphQL API 的服务中,我使用了一个第三方库去解析 GraphQL 的查询语句,并使用自定义函数处理其中的某些字段。这个库提供了一个函数指针类型,我们只需要将自定义函数传入其中即可。
具体来说,代码片段如下:
立即学习“go语言免费学习笔记(深入)”;
type fieldResolver func(ctx context.Context, obj interface{}, args map[string]interface{}) (interface{}, error)
type Resolver struct {
// ...
fieldResolvers map[string]fieldResolver
// ...
}
func (r *Resolver) AddFieldResolver(fieldName string, fr fieldResolver) {
r.fieldResolvers[fieldName] = fr
}AddFieldResolver 方法用于向 Resolver 类型的结构体中添加一个字段解析的函数指针。同时,这个 Resolver 类型的结构体还实现了一个 GraphQL 的 Resolver 接口。
在我的实现中,我向这个 Resolver 类型的结构体中添加了两个字段解析函数指针。这两个函数指针分别由 name 和 created_at 两个字段名决定。
func (r *Resolver) BaseQuery() QueryResolver {
return &queryResolver{r}
}
func (r *Resolver) name(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
// handler for name field
}
func (r *Resolver) created_at(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
// handler for created_at field
}
func (r *Resolver) initFieldResolvers() {
r.fieldResolvers = map[string]fieldResolver{
"name": r.name,
"created_at": r.created_at,
}
}这两个字段解析函数指针的初始化是在 initFieldResolvers 方法中完成的,这个方法将被 Resolver 类型的结构体的构造函数中调用。
我们还需要在 Resolver 类型的结构体中实现具体的 Resolver 接口,具体方法如下:
type queryResolver struct{ *Resolver }
func (r *queryResolver) Name(ctx context.Context, obj *types.User) (string, error) {
resolver, ok := r.fieldResolvers["name"]
if !ok {
return "", fmt.Errorf("resolver not found")
}
result, err := resolver(ctx, obj, nil)
if err != nil {
return "", err
}
return result.(string), nil
}
func (r *queryResolver) CreatedAt(ctx context.Context, obj *types.User) (string, error) {
resolver, ok := r.fieldResolvers["created_at"]
if !ok {
return "", fmt.Errorf("resolver not found")
}
result, err := resolver(ctx, obj, nil)
if err != nil {
return "", err
}
return result.(string), nil
}这动态调用了我们之前注册过的解析函数指针,也就是 name 和 created_at 两个函数。
然而,在测试过程中,我却发现这个实现非常不稳定,有时能够正常工作,但另外的时候却报错“resolver not found”。
原因分析
对于这种情况,我首先想到了函数指针失效的可能性。在实际的开发中,我们经常会遇到类似的问题:将函数指针存储在一个结构体变量里,然后在其他地方调用这个变量的值,却发现指针已经失效了。
在 Go 语言中,和其他语言一样,函数是一等公民,而函数指针也作为一个变量进行存储。通常情况下,函数指针并不会失效,理论上在内存中保存的是一个可以被调用的指针地址,一直到程序结束这个指针才会被回收。
然而在我们的场景中,由于这个 Resolver 类型的结构体是在多个协程下共享的,存在并发访问,同时也存在协程退出释放内存的情况。这就可能导致函数指针失效的情况出现。
解决方案
解决函数指针失效的问题,本质上是要避免指针失效的情况。在 golang 的并发编程中,有一些技术手段可以保证某个数据在并发访问时不会出错。接下来,我们将介绍两种常见的技巧,用于避免函数指针失效的情况。
对于上述代码中的 Resolver 类型的结构体,我们可以使用 sync.RWMutex 类型来保护 fieldResolvers 字段的并发读写。这样我们就能保证在读取 fieldResolvers 字段的情况下,不会发生竞态条件。
同时,我们也可以使用一个函数指针的切片来代替 map 类型,在读取函数指针的情况下,不再需要对 map 类型进行访问,从而避免了竞态条件的发生。
具体代码如下:
type Resolver struct {
sync.RWMutex
fieldResolvers []*fieldResolver
}
func (r *Resolver) AddFieldResolver(fr *fieldResolver) {
r.Lock()
defer r.Unlock()
r.fieldResolvers = append(r.fieldResolvers, fr)
}
func (r *Resolver) name(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
// handler for name field
}
func (r *Resolver) created_at(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
// handler for created_at field
}
func (r *Resolver) initFieldResolvers() {
r.AddFieldResolver(&r.name)
r.AddFieldResolver(&r.created_at)
}在这里,我把 fieldResolvers 的类型从 map[string]fieldResolver 改为了 []*fieldResolver 。使用指针类型可以避免多余的内存分配和数据拷贝。
另一种避免函数指针失效的技巧是将函数指针存储在通道中。具体来说,当我们需要调用函数指针时,我们可以向通道中发送这个函数指针,并在另外的协程中等待通道返回这个函数指针之后再进行调用。
这样一来,就可以保证函数指针不会失效,并且可以避免在协程退出时的内存释放等问题。
具体代码如下:
type Resolver struct {
fieldResolvers chan *fieldResolver
// ...
}
func (r *Resolver) AddFieldResolver(fr *fieldResolver) {
r.fieldResolvers <- fr
}
func (r *Resolver) initFieldResolvers() {
// ...
go func() {
for fr := range r.fieldResolvers {
if fr != nil {
// call the function pointer
}
}
}()
}在这里,我将 fieldResolvers 的类型改为了 chan *fieldResolver,并通过一个协程调用这个通道中的函数指针。
结论
对于在 golang 中遇到的函数指针失效的问题,我们需要注意程序的并发性和内存释放问题。在避免竞态条件和内存管理的问题上,我们可以利用 golang 强大的并发编程特性,如 RWMutex 和 chan 等。
同时,我们也需要注意使用指针类型和避免不必要的内存分配和数据拷贝,以尽可能地减少函数指针失效的概率。
以上就是golang函数指针失效的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号