Go語言 一些恐慌/恢復(fù)用例

2023-02-16 17:38 更新

恐慌和恢復(fù)(panic/recover)已經(jīng)在之前的文章中介紹過了。 下面將展示一些恐慌/恢復(fù)用例。

用例1:避免恐慌導(dǎo)致程序崩潰

這可能是最常見的panic/recover用例了。 此用例廣泛地使用于并發(fā)程序中,尤其是響應(yīng)大量用戶請(qǐng)求的應(yīng)用。

一個(gè)例子:

package main

import "errors"
import "log"
import "net"

func main() {
	listener, err := net.Listen("tcp", ":12345")
	if err != nil {
		log.Fatalln(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err)
		}
		// 在一個(gè)新協(xié)程中處理客戶端連接。
		go ClientHandler(conn)
	}
}

func ClientHandler(c net.Conn) {
	defer func() {
		if v := recover(); v != nil {
			log.Println("捕獲了一個(gè)恐慌:", v)
			log.Println("防止了程序崩潰")
		}
		c.Close()
	}()
	panic("未知錯(cuò)誤") // 演示目的產(chǎn)生的一個(gè)恐慌
}

運(yùn)行此服務(wù)器程序,并在另一個(gè)終端窗口運(yùn)行telnet localhost 12345,我們可以觀察到服務(wù)器程序不會(huì)因?yàn)榭蛻暨B接處理協(xié)程中的產(chǎn)生的恐慌而導(dǎo)致崩潰。

如果我們?cè)谏侠胁徊东@客戶連接處理協(xié)程中的潛在恐慌,則這樣的恐慌將使整個(gè)程序崩潰。

用例2:自動(dòng)重啟因?yàn)榭只哦顺龅膮f(xié)程

當(dāng)在一個(gè)協(xié)程將要退出時(shí),程序偵測(cè)到此協(xié)程是因?yàn)橐粋€(gè)恐慌而導(dǎo)致此次退出時(shí),我們可以立即重新創(chuàng)建一個(gè)相同功能的協(xié)程。 一個(gè)例子:

package main

import "log"
import "time"

func shouldNotExit() {
	for {
		time.Sleep(time.Second) // 模擬一個(gè)工作負(fù)載
		// 模擬一個(gè)未預(yù)料到的恐慌。
		if time.Now().UnixNano() & 0x3 == 0 {
			panic("unexpected situation")
		}
	}
}

func NeverExit(name string, f func()) {
	defer func() {
		if v := recover(); v != nil { // 偵測(cè)到一個(gè)恐慌
			log.Printf("協(xié)程%s崩潰了,準(zhǔn)備重啟一個(gè)", name)
			go NeverExit(name, f) // 重啟一個(gè)同功能協(xié)程
		}
	}()
	f()
}

func main() {
	log.SetFlags(0)
	go NeverExit("job#A", shouldNotExit)
	go NeverExit("job#B", shouldNotExit)
	select{} // 永久阻塞主線程
}

用例3:使用panic/recover函數(shù)調(diào)用模擬長程跳轉(zhuǎn)

有時(shí),我們可以使用panic/recover函數(shù)調(diào)用來模擬跨函數(shù)跳轉(zhuǎn),盡管一般這種方式并不推薦使用。 這種跳轉(zhuǎn)方式的可讀性不高,代碼效率也不是很高,唯一的好處是它有時(shí)可以使代碼看上去不是很啰嗦。

在下面這個(gè)例子中,一旦一個(gè)恐慌在一個(gè)內(nèi)嵌函數(shù)中產(chǎn)生,當(dāng)前協(xié)程中的執(zhí)行將會(huì)跳轉(zhuǎn)到延遲調(diào)用處。

package main

import "fmt"

func main() {
	n := func () (result int)  {
		defer func() {
			if v := recover(); v != nil {
				if n, ok := v.(int); ok {
					result = n
				}
			}
		}()

		func () {
			func () {
				func () {
					// ...
					panic(123) // 用恐慌來表示成功返回
				}()
				// ...
			}()
		}()
		// ...
		return 0
	}()
	fmt.Println(n) // 123
}

用例4:使用panic/recover函數(shù)調(diào)用來減少錯(cuò)誤檢查代碼

一個(gè)例子:

func doSomething() (err error) {
	defer func() {
		err = recover()
	}()

	doStep1()
	doStep2()
	doStep3()
	doStep4()
	doStep5()

	return
}

// 在現(xiàn)實(shí)中,各個(gè)doStepN函數(shù)的原型可能不同。
// 每個(gè)doStepN函數(shù)的行為如下:
// * 如果已經(jīng)成功,則調(diào)用panic(nil)來制造一個(gè)恐慌
//   以示不需繼續(xù);
// * 如果本步失敗,則調(diào)用panic(err)來制造一個(gè)恐慌
//   以示不需繼續(xù);
// * 不制造任何恐慌表示繼續(xù)下一步。
func doStepN() {
	...
	if err != nil {
		panic(err)
	}
	...
	if done {
		panic(nil)
	}
}

下面這段同功能的代碼比上面這段代碼看上去要啰嗦一些。

func doSomething() (err error) {
	shouldContinue, err := doStep1()
	if !shouldContinue {
		return err
	}
	shouldContinue, err = doStep2()
	if !shouldContinue {
		return err
	}
	shouldContinue, err = doStep3()
	if !shouldContinue {
		return err
	}
	shouldContinue, err = doStep4()
	if !shouldContinue {
		return err
	}
	shouldContinue, err = doStep5()
	if !shouldContinue {
		return err
	}

	return
}

// 如果返回值err不為nil,則shouldContinue一定為true。
// 如果shouldContinue為true,返回值err可能為nil或者非nil。
func doStepN() (shouldContinue bool, err error) {
	...
	if err != nil {
		return false, err
	}
	...
	if done {
		return false, nil
	}
	return true, nil
}

但是,這種panic/recover函數(shù)調(diào)用的使用方式一般并不推薦使用,因?yàn)樗男事缘鸵恍⑶疫@種用法不太符合Go編程習(xí)俗。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)