是否有可能为代表避免使用GC?
我正在建立一个任务系统.我有一个带有本地任务队列的N-Threads.任务队列基本上只是一个Array!Fiber tasks.因为不鼓励将光纤发送到不同的线程,所以我向一个线程发送一个闭包/委托,从该委托创建光纤并将其放入数组中tasks.
现在我发送的代表是捕获变量的委托.
//Some Pseudo code
auto f = //some function;
auto cell = Cell(...);
auto del = () {
let res = f();
cell.write(res);
}
send(del);
Run Code Online (Sandbox Code Playgroud)
}
现在,单元格被堆分配并与原子计数器同步.然后,我可以检查原子计数器cell是否已经达到0,如果它已经,我可以安全地读取它.
问题是代理人捕获变量,在GC上分配变量.现在我只分配一个指针,这可能不是一个大问题,但我仍然想避免使用GC.
我该怎么做?
您可能已经知道这一切,但这是一个常见问题,所以我将写一些细节.
首先,让我们了解委托是什么.就像切片只是一个与长度配对的C数据指针一样,委托只是一个与函数指针配对的C数据指针.它们一起传递给期望它们的函数,就像定义它们一样
struct d_delegate {
void* ptr; // yes, it is actually typed void*!
T* funcptr; // this is actually a function pointer
};
Run Code Online (Sandbox Code Playgroud)
(注意,当你尝试在类方法中使用嵌套委托时,有一个数据ptr的原因是一些编译器错误背后的原因!)
这void*就是指向数据并且与切片相似的内容,它可以来自各种各样的地方:
Object obj = new Object();
string delegate() dg = &obj.toString;
Run Code Online (Sandbox Code Playgroud)
在这一点上,dg.ptr指向obj,恰好是一个垃圾收集类对象,但只是因为我new在上面编辑它.
struct MyStruct {
string doSomething() { return "hi"; }
}
MyStruct obj;
string delegate() dg = &obj.doSomething;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,obj由于我在上面的分配方式而生活在堆栈上,因此dg.ptr也指向该临时对象.
无论事情是委托或不说任何关于使用它的内存分配方案 - 这无疑是危险的,因为通过委托你可能会指向一个临时对象,你用它完成之前即会消失!(这就是为什么要使用GC的主要原因,以帮助防止这种免费使用后的错误.)
那么,如果代表可以来自任何对象,为什么他们被认为是GC那么多呢?好吧,当编译器认为委托的生命周期比外部函数长时,自动生成的闭包可以将局部变量复制到GC段.
void some_function(void delegate() dg);
void foo() {
int a;
void nested() {
a++;
}
some_function(&nested);
}
Run Code Online (Sandbox Code Playgroud)
在这里,编译器会将变量复制a到GC段,因为它假设some_function将保留它的副本并希望防止使用后释放的错误(这是一个很难调试,因为它经常导致内存损坏!)以及内存泄漏.
但是,如果您通过scope在委托定义上使用关键字向编译器保证自己可以正确地执行此操作,那么它将信任您并将本地保留在原来的位置:
void some_function(scope void delegate() dg);
Run Code Online (Sandbox Code Playgroud)
保持其余部分相同,它将不再分配副本.在函数定义方面这样做是最好的,因为作为函数作者,您可以确保不实际保留副本.
在使用方面,您也可以标记它的范围:
void foo() {
int a;
void nested() {
a++;
}
// this shouldn't allocate either
scope void delegate() dg = &nested;
some_function(&dg);
}
Run Code Online (Sandbox Code Playgroud)
所以,唯一的时间内存自动由GC分配时局部变量是由已经采取了地址的嵌套函数中使用没有的scope关键字.
请注意,() => whatever和() { return foo; }语法只是命名嵌套函数的简写,其地址是自动获取的,因此它们的工作方式与上面相同.dg = {a++;};与dg = &nested;上面相同.
因此,从这个关键外卖为你的是,如果你想手动分配一个委托,你只需要手动分配一个对象,并从其中的一个方法,而不是自动捕获变量作出委托!但是,您需要跟踪生命周期并正确释放它.这是棘手的部分.
所以对于你的例子:
auto del = () {
let res = f();
cell.write(res);
};
Run Code Online (Sandbox Code Playgroud)
你可以把它翻译成:
struct Helper {
T res;
void del() {
cell.write(res);
}
}
Helper* helper = malloc(Helper.sizeof);
helper.res = res; // copy the local explicitly
send(&helper.del);
Run Code Online (Sandbox Code Playgroud)
然后,在接收方,不要忘记free(dg.ptr);当你完成所以你不泄漏它.
或者,更好的是,如果您可以更改send为实际接受Helper对象,则根本不需要分配它,您可以按值传递它.
在我看来,你可以在该指针中打包一些其他数据以便就地传递其他数据,但这可能是黑客攻击并且可能是未定义的行为.试试看,如果你想玩:)