我有一个关于使用setjmp和longjump创建可以彼此独立运行的函数堆栈的问题。
这里,B()的函数堆栈似乎在A的函数堆栈之上,所以当A超出作用域,我尝试长跳转到B()时,代码segfaults。
#include <stdio.h>
#include <setjmp.h>
#include <iostream>
using namespace std;
jmp_buf bufferA, bufferB;
void routineB(); // forward declaration
void routineA()
{
int r ;
printf("(A1)\n");
r = setjmp(bufferA);
if (r == 0) routineB();
printf("(A2) r=%d\n",r);
r = setjmp(bufferA);
if (r == 0) longjmp(bufferB, 20001);
printf("(A3) r=%d\n",r);
r = setjmp(bufferA);
if (r == 0) longjmp(bufferB, 20002);
printf("(A4) r=%d\n",r);
}
void routineB()
{
int r;
printf("(B1)\n");
r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10001);
printf("(B2) r=%d\n", r);
r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10002);
printf("(B3) r=%d\n", r);
r = setjmp(bufferB);
if (r == 0) longjmp(bufferA, 10003);
cout << "WHAT" << endl;
}
int main(int argc, char **argv)
{
routineA();
longjmp(bufferB, 123123);
return 0;
}
我在想,我们可以让每个协程(在这个例子中是B和A)跳回到一个主函数,然后再跳到一个协程,如果它们是活的。这会起作用吗?或者这也会导致segfault,因为一些可能死的协程的堆栈在想要运行的协程的堆栈之上?
如果它看起来确实应该segfault,那么如何在C++中实现这样独立的coutroutines(当其他coutroutines已经死亡时,它们可以运行,并且不依赖于彼此)?
注意:我不想使用swapcontext、makecontext、setcontext和getcontext,因为它们已经过时了。这篇文章的目的是帮助我找到这些函数的替代方法
先谢了!
2条答案
按热度按时间kb5ga3dv1#
它应该能起作用,但需要一些预防措施。它在90年代曾经起作用。
只要栈中较旧的线程不返回,它就可以工作,那么就不会有问题。对于生成器或线程,这意味着它以某个最终产出终止,但不返回。
我记得我曾要求IBM修复他们的AIX实现setjmp/longjmp,以便它停止检查(和禁止)其他实现允许的有用行为,这是我当时发现的实现协作线程的最可移植的方法。
使用基于状态机的堆栈分配器可以做得更好,以便将不同大小的堆栈分配/释放给不同的线程。
这是非常棘手的,但据我所知,我记得Matz(来自Ruby)问我几年后的代码,但唉,我没有更多的访问它的时间。
无论如何,这在今天并不重要,因为pthread完成了这项工作。
5anewei62#
快速和肮脏的黑客在Windows上。抱歉混乱的命名和混乱的代码。
此外,微软和setjmp/longjmp可能不是一个很好的匹配。如果我附近有一个Unix机器,我会在那个机器上做。
而且我可能还得多研究一下堆栈复制,以确保它能正确工作。
编辑:要检查堆栈更改后要修改哪些寄存器,可以查阅编译器手册.
代码从main开始()通过设置线程框架来实现。它通过处理主线程来实现(此时唯一的线程)作为其他线程中的一个线程。因此,存储主线程的上下文并将其放在thread_list中。然后我们调度()来让一个线程运行。这是在initThreading()中完成的。因为唯一运行的线程是主线程,所以我们将在main()函数中继续。
main函数要做的下一件事是将函数指针作为参数的startThread(以及要发送到func的arg NULL)。()函数的作用是为新线程的堆栈分配内存(每个线程都需要自己的堆栈)。分配之后,我们将运行线程的上下文保存到新线程上下文缓冲区中(jmp_buf)并更改堆栈指针(以及所有其他应该被改变的寄存器),使它指向新创建的堆栈。然后我们将这个新线程添加到等待线程列表中。然后我们从函数返回。我们仍然在主线程中。
在主线程中,我们执行thread_yield(),它会说“好吧,我不想再运行了,让别人运行吧!"thread_yield函数存储当前上下文,并将主线程放在thread_list的后面,然后我们进行调度。
Schedule()从thread_list中获取下一个线程,并对保存在线程buf(jmp_buf)中的上下文执行longjmp操作,在这种情况下,线程将继续在setjmp的else子句中运行,我们在这里存储了上下文。
它将一直运行,直到我们执行thread_yield,然后我们将执行相同的操作,但取而代之的是主线程并对其保存的缓冲区执行longjmp,等等...
如果您想做得更好并且想要时间片,可以实现一些警报来保存当前线程上下文,然后调用schedule()。
明白了吗?