Rumah > Artikel > pembangunan bahagian belakang > penunjuk fungsi golang tidak sah
Dalam golang, penunjuk fungsi ialah jenis penting kegunaan utamanya termasuk fungsi panggil balik, pemuatan dinamik fungsi perpustakaan, dll. Oleh itu, kegagalannya akan memberi kesan yang serius terhadap ketepatan program.
Walau bagaimanapun, dalam persekitaran pembangunan sebenar, kami sering menghadapi situasi di mana penunjuk fungsi menjadi tidak sah, menyebabkan ralat aneh dalam atur cara. Artikel ini akan mengambil kes khusus sebagai contoh untuk menganalisis sebab kegagalan penunjuk fungsi dan membincangkan cara mengelakkan situasi ini.
Analisis Kes
Dalam perkhidmatan API GraphQL, saya menggunakan perpustakaan pihak ketiga untuk menghuraikan pernyataan pertanyaan GraphQL dan menggunakan fungsi tersuai untuk memproses beberapa medan. Pustaka ini menyediakan jenis penunjuk fungsi, kita hanya perlu menghantar fungsi tersuai ke dalamnya.
Secara khusus, coretan kod adalah seperti berikut:
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 }
Kaedah AddFieldResolver digunakan untuk menambah penuding fungsi resolusi medan pada struktur jenis Resolver. Pada masa yang sama, struktur jenis Penyelesai ini juga melaksanakan antara muka Penyelesai GraphQL.
Dalam pelaksanaan saya, saya menambahkan dua penunjuk fungsi resolusi medan pada struktur jenis Penyelesai ini. Kedua-dua penunjuk fungsi ini ditentukan oleh dua nama medan dan create_at masing-masing.
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, } }
Pemulaan kedua-dua penunjuk fungsi resolusi medan ini diselesaikan dalam kaedah initFieldResolvers, yang akan dipanggil dalam pembina struktur jenis Resolver.
Kami juga perlu melaksanakan antara muka Penyelesai khusus dalam struktur jenis Penyelesai Kaedah khusus adalah seperti berikut:
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 }
Ini secara dinamik memanggil penunjuk fungsi parsing yang kami daftarkan sebelum ini, iaitu nama. dan create_at dua fungsi.
Walau bagaimanapun, semasa proses ujian, saya mendapati pelaksanaan ini sangat tidak stabil Kadangkala ia boleh berfungsi seperti biasa, tetapi pada masa lain ia melaporkan ralat "penyelesai tidak dijumpai".
Analisis Sebab
Untuk situasi ini, saya mula-mula memikirkan kemungkinan penunjuk fungsi tidak sah. Dalam pembangunan sebenar, kita sering menghadapi masalah yang sama: menyimpan penunjuk fungsi dalam pembolehubah struktur, dan kemudian memanggil nilai pembolehubah ini di tempat lain, hanya untuk mendapati penunjuk telah tamat tempoh.
Dalam bahasa Go, seperti bahasa lain, fungsi ialah warga kelas pertama dan penunjuk fungsi juga disimpan sebagai pembolehubah. Dalam keadaan biasa, penunjuk fungsi tidak akan luput Secara teori, apa yang disimpan dalam memori ialah alamat penunjuk yang boleh dipanggil Penunjuk ini tidak akan dikitar semula sehingga tamat program.
Walau bagaimanapun, dalam senario kami, memandangkan struktur jenis Penyelesai ini dikongsi antara berbilang coroutine, terdapat akses serentak dan terdapat juga situasi di mana coroutine keluar untuk melepaskan memori. Ini boleh menyebabkan penunjuk fungsi menjadi tidak sah.
Penyelesaian
Untuk menyelesaikan masalah ketidaksahihan penunjuk fungsi, intipatinya adalah untuk mengelakkan situasi ketidaksahihan penunjuk. Dalam pengaturcaraan serentak golang, terdapat beberapa cara teknikal untuk memastikan bahawa data tertentu tidak akan salah apabila diakses secara serentak. Seterusnya, kami akan memperkenalkan dua teknik biasa untuk mengelakkan penunjuk fungsi tidak sah.
Untuk struktur jenis Resolver dalam kod di atas, kita boleh menggunakan jenis sync.RWMutex untuk melindungi medan FieldResolvers Bacaan serentak dan menulis. Dengan cara ini kita boleh memastikan bahawa tiada keadaan perlumbaan akan berlaku apabila membaca medan fieldResolvers.
Pada masa yang sama, kita juga boleh menggunakan sekeping penunjuk fungsi dan bukannya jenis peta Apabila membaca penunjuk fungsi, tidak perlu mengakses jenis peta, sekali gus mengelakkan berlakunya keadaan perlumbaan.
Kod khusus adalah seperti berikut:
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) }
Di sini, saya menukar jenis fieldResolver daripada map[string]fieldResolver kepada []*fieldResolver. Menggunakan jenis penuding mengelakkan peruntukan memori yang berlebihan dan penyalinan data.
Helah lain untuk mengelakkan ketidaksahihan penunjuk fungsi ialah menyimpan penunjuk fungsi dalam saluran. Khususnya, apabila kita perlu memanggil penuding fungsi, kita boleh menghantar penuding fungsi ke saluran dan menunggu dalam coroutine lain untuk saluran mengembalikan penuding fungsi sebelum membuat panggilan.
Dengan cara ini, ia boleh memastikan penunjuk fungsi tidak akan luput, dan boleh mengelakkan masalah seperti pelepasan memori apabila coroutine keluar.
Kod khusus adalah seperti berikut:
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 } } }() }
Di sini, saya menukar jenis fieldResolvers kepada chan *fieldResolver, dan memanggil penuding fungsi dalam saluran ini melalui coroutine.
Kesimpulan
Untuk masalah penunjuk fungsi tidak sah yang dihadapi dalam golang, kita perlu memberi perhatian kepada isu konkurensi dan pelepasan memori program. Untuk mengelakkan keadaan perlumbaan dan isu pengurusan memori, kami boleh memanfaatkan ciri pengaturcaraan serentak yang berkuasa golang, seperti RWMutex dan chan.
Pada masa yang sama, kita juga perlu memberi perhatian kepada penggunaan jenis penunjuk dan mengelakkan peruntukan memori yang tidak perlu dan penyalinan data untuk meminimumkan kebarangkalian kegagalan penunjuk fungsi.
Atas ialah kandungan terperinci penunjuk fungsi golang tidak sah. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!