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

2023-02-16 17:38 更新

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

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

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

一個例子:

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)
		}
		// 在一個新協(xié)程中處理客戶端連接。
		go ClientHandler(conn)
	}
}

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

運行此服務(wù)器程序,并在另一個終端窗口運行telnet localhost 12345,我們可以觀察到服務(wù)器程序不會因為客戶連接處理協(xié)程中的產(chǎn)生的恐慌而導(dǎo)致崩潰。

如果我們在上例中不捕獲客戶連接處理協(xié)程中的潛在恐慌,則這樣的恐慌將使整個程序崩潰。

用例2:自動重啟因為恐慌而退出的協(xié)程

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

package main

import "log"
import "time"

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

func NeverExit(name string, f func()) {
	defer func() {
		if v := recover(); v != nil { // 偵測到一個恐慌
			log.Printf("協(xié)程%s崩潰了,準備重啟一個", name)
			go NeverExit(name, f) // 重啟一個同功能協(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)

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

在下面這個例子中,一旦一個恐慌在一個內(nèi)嵌函數(shù)中產(chǎn)生,當前協(xié)程中的執(zhí)行將會跳轉(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)用來減少錯誤檢查代碼

一個例子:

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

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

	return
}

// 在現(xiàn)實中,各個doStepN函數(shù)的原型可能不同。
// 每個doStepN函數(shù)的行為如下:
// * 如果已經(jīng)成功,則調(diào)用panic(nil)來制造一個恐慌
//   以示不需繼續(xù);
// * 如果本步失敗,則調(diào)用panic(err)來制造一個恐慌
//   以示不需繼續(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)用的使用方式一般并不推薦使用,因為它的效率略低一些,并且這種用法不太符合Go編程習(xí)俗。


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號