如何获取对延迟函数的引用?

huangapple go评论77阅读模式
英文:

How can I get a reference to a defer function?

问题

这篇文章中提到:“defer语句将一个函数调用推入一个列表中。”我想知道我是否可以从程序的其他地方访问该列表中的元素,并调用它们?我可以多次调用它们吗?我假设我有一个对具有defer行为的函数的引用(如果有帮助的话)。

所以,这是一个简短的示例,展示了我想要做的事情:

func main() {
    doStuff := func() {
        // 打开数据库连接
        // 写入临时文件
        // 等等...
    
        defer func() {
            // 关闭数据库连接
            // 删除临时文件
            // 等等...
        }()
    }

    AwesomeApplication(doStuff)
}

func AwesomeApplication(doStuff func()) {
    // 现在,我能否在`doStuff`函数内部获取对defer函数的引用?
    // 不,我不能只是在某个地方定义defer函数并将其传递给`doStuff`。
    // 将其视为我想满足的一种好奇心,而不是真正的用例。
}

希望能帮到你!

英文:

This article states: "A defer statement pushes a function call onto a list." I'm wondering if I can access the elements in that list from another place in my program and then invoke them? Can I invoke them multiple times? I'm assuming that I have a reference to the function that has defer behavior (if that helps).

So, here's a short example of what I want to do:

func main {
	doStuff = func() {
		// open database connections
		// write temporary files
		// etc...

		defer func() {
			// close database connections
			// delete temporary files
			// etc...
		}()
	}

	AwesomeApplication(doStuff)
}

func AwesomeApplication(doStuff func()) {
	// Now, can I get a reference to the defer function within `doStuff`?
	// No, I can't just define the defer function somewhere an pass it
	// with `doStuff`.  Think of this as a curiosity I want to satisfy,
	// not a real use case.
}

答案1

得分: 11

defer调用存储在一个完全特定于实现的“列表”中,因此您没有可靠的方法来获取此列表。关于*g编译器系列(尽管有点旧)的实现细节可以在Russ Cox的研究博客中找到。

延迟函数与当前goroutine相关联(g->Defer),并且(对于*g系列)由当前堆栈指针标识。如果当前堆栈帧与顶部Defer条目中存储的堆栈帧匹配,则调用此函数。

有了这个知识,使用cgo是可以访问延迟函数列表的。您需要知道:

  • 当前堆栈指针
  • 函数的地址
  • 当前goroutine

然而,我不建议使用这种方法。
对于您描述的用例,一个通用的解决方案是拥有这样一个函数:

func setupRoutines() (setUp, tearDown func()) {
    // 存储数据库连接对象等

    return func() { /* 连接数据库等 */ }, func() { /* 关闭数据库等 */ }
}

在您的代码中,您可以共享tearDown函数,并使用defer调用它。
这样,您仍然可以将所有数据库连接等内容保持本地,但可以共享初始化/断开连接函数。

可供尝试的代码

如果您真的对使用unsafe和C进行实验感兴趣,可以使用以下代码作为模板。

inspect/runtime.c:

// +build gc
#include <runtime.h>

void ·FirstDeferred(void* foo) {
    foo = g->defer->fn;

    FLUSH(&foo);
}

inspect/inspect.go

package inspect

import "unsafe"

func FirstDeferred() unsafe.Pointer

defer.go

package main

import "defer/inspect"

func f(a, b int) {
    println("deferred f(", a, b, ")")
}

func main() {
    defer f(1, 2)
    println(inspect.FirstDeferred())
}

这段代码(基于这个)让您可以访问当前goroutine(g),从而访问其defer属性。因此,您应该能够访问函数的指针并将其包装在go的FuncVal中并返回。

英文:

The 'list' where to defer calls are stored are fully implementation specific so you have no reliable way of getting to this list.<sup>1,2</sup> The implementation details for the *g compiler family (albeit a bit older) can be found in Russ Cox' research blog.

Deferred functions are associated with the current goroutine (g-&gt;Defer) and
(in case of *g family) identified by the current stack pointer. If the current stack frame matches
the stack frame stored in the topmost Defer entry, this function is called.

With this knowledge it is possible to access the list of deferred functions using cgo.
You need to know

  • the current stack pointer
  • the address of the function
  • the current goroutine

However, I don't recommend using this.
A general solution for the use case you're describing would be to have a function like this:

func setupRoutines() (setUp, tearDown func()) {
    // store db connection object and such

    return func() { /* connect db and such */ }, func() { /* close db and such */ }
}

In your code you could then share the tearDown function, which would be called using defer.
This way you still have the bonus of having all your database connections and such local but you're
able to share the initialization/disconnect functions.

Fiddle to play with

If you're really interested in playing around with unsafe and C, you can use the following code as a template.

inspect/runtime.c:

// +build gc
#include &lt;runtime.h&gt;

void &#183;FirstDeferred(void* foo) {
	foo = g-&gt;defer-&gt;fn;

	FLUSH(&amp;foo);
}

inspect/inspect.go

package inspect

import &quot;unsafe&quot;

func FirstDeferred() unsafe.Pointer

defer.go

package main

import &quot;defer/inspect&quot;

func f(a, b int) {
	println(&quot;deferred f(&quot;, a, b, &quot;)&quot;)
}

func main() {
	defer f(1, 2)
	println( inspect.FirstDeferred() )
}

This code (based on this) gives you access to the current go routine (g) and therefore
the defer attribute of it. Therefore you should be able to access the pointer to the function
and wrap it in a go FuncVal and return it.

huangapple
  • 本文由 发表于 2013年8月27日 07:06:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/18454468.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定