I previously know Golang also very superficial time, has been to goroutine thunderclap piercing, I believe many classmates and I, will think in Go code, Goroutine figure everywhere, in fact, is not so.
The two days involved a small project in the financial sector, which golang a small module in an old system from PHP code. Because the colleague in charge of the refactoring had only PHP experience, he sent me and another colleague to help. This morning the director came to see the progress, inadvertently looked at my code, immediately pointed out a serious bug, let me find a knowledge blind spot, I think it is worth sharing.
Process
Yesterday afternoon wrote an grpc
interface, according to user_id
query a table from the database user_config
, get a city_ids
field, is a city_id
composition of the string, and then split
processed after the city
table to take the city data, the approximate process is similar to this:
func GetCities(userID int64) ([]*cityData, error) { var ( strCityIDs string CityIDs []string ret []*cityData ) strCityIDs, _ = userConfig.GetCityIDs(userID) //从user_config表查询city_id字段 CityIDs = strings.Split(strCityIDs, sep) //处理成id数组 err = city.Find(CityIDs, &ret) //从city表查出数据 return ret, err}
Speaking plainly is a has_many
relationship. Because the city
table will almost never change, early on the company, I think I can add a cache, so changed to:
func GetCities(userID int64) ([]*cityData, error) { var ( strCityIDs string CityIDs []string ret []*cityData ) strCityIDs, _ = userConfig.GetCityIDs(userID) //从user_config表查询city_id字段 err := cache.Get(prefix+strCityIDs, &ret) //先从缓存拿数据 if err == nil { return ret, nil } CityIDs = strings.Split(strCityIDs, sep) //处理成id数组 err = city.Find(CityIDs, &ret) //从city表查出数据 if err == nil { ok := cache.Set(prefix+strCityIDs, &ret, 12*time.Hour) //存入缓存 if !ok { doNothing() } } return ret, err}
After changing the "Turing" a move, think of oneself almost did not see in the company project saw the emergence of the go
key words, oneself also basically did not in the production actually used goroutine
, so cache.Set
changed to go cache.Set
. I think that the success of the cache does not affect the main process (even if it fails I do not do anything), so it can be given to the association to do, and so that goroutine
the Lord can return faster.
Then the director came over.
Chatted two sentences, suddenly pointed to the code and said to me: "Here is wrong, can not use the co-process!" ”
Me: "Why?" ”
Director: "Because the panic inside the process will make the whole crash." ”
I was even more puzzled: "But I middleware Riga recover Ah, will catch panic." ”
middleware
Code:
func (*Interceptor) Method(ctx context.Context, srvInfo *core.SrvInfo, req interface{}, handler func(context.Context, interface{}) (interface{}, error)) (ret interface{}, err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error: %v", p) } }() ret, err = handler(ctx, req) //所有下层逻辑全部在这个函数里分发,所以我错误地认为任何panic都能在这里recover return ret, err}
Director: "Goroutine occurs panic, only oneself can recover, other goroutine is not caught, this is common sense ah." ”
Me: "..."
Scared I did not dare to say anything, quickly go
deleted the key words, and then the director left, immediately after the Internet to study a wave of goroutine, panic, recover relationship between, the following is the conclusion.
Conclusion
First, to be clear, it panic
will stop the whole process, not just the current one, which means that the goroutine
whole program is cool (and I now think that's the goroutine
reason why there's no flooding in the code, and the other reason is, I think that in the case of the cpu
nuclear all running up, more goroutine
can also be concurrent and not parallel).
Second, panic
is the orderly, controllable stop program, not bam down, so we can also use recover
remediation.
Then, it recover
can only defer
take effect in the inside, if not in the defer
call, will return directly nil
.
Finally, it is important to note that goroutine
when it happens, it panic
only calls itself defer
, so even if the Lord goroutine
writes recover
logic, it cannot be saved in goroutine
the other panic
.
So, the previous writing go cache.Set
is very dangerous, because it cache
did not do anything recover
, once it appears panic
, will affect the entire system.
Let's say I'm going to put this in the go
keyword implementation (obviously I'm not such a person), the code can be changed to:
go func () {defer func () {if r: = Recover (); r! = nil {fmt. Println ("Don t worry, I can take care of Myself")}} () cache. Set (Prefix+strcityids, &ret, 12*time. Hour)//cache} ()