1

我正在尝试在 c 中实现简单的用户级线程库。当一个线程启动并且该线程调用第二个线程时。这第二个线程运行正确,但是当它退出程序崩溃时。这是我的编码。

//**********************************************
#include <setjmp.h>

typedef void *any_ptr;
/* Define Boolean type, and associated constants. */
typedef int Boolean;
typedef void (*ThreadFunc)(any_ptr);

#define TRUE ((Boolean)1);
#define FALSE ((Boolean)0);

typedef struct TheadSystem
{
  queue<any_ptr> readyQ;
  // currently executing thread
  jmp_buf lastContext; // location on which the system jumps after all threads have exited
  char name[30]; // optional name string of a thread, may be used for debugging
  jmp_buf context;   // saved context of this thread
  signal_t *sig; // signal that wakes up a waiting thread
  ThreadFunc func; // function that this thread started executing
  any_ptr    arg;
}TheadSystem;

void t_start(ThreadFunc f, any_ptr v, char *name);
void t_yield();
void block();
void unblock();
void t_sig(Condition cond, any_ptr val, Boolean queue_signal);
void t_fork(ThreadFunc f, any_ptr v, char *name);
void t_exit(int val);

我的threads.h实现

#include "threads.h"
#include<iostream>
#include<queue>

using namespace std;
TheadSystem th;

queue<any_ptr> blocked_queue;
jmp_buf set_env,ready_env,yeild_buf;

void t_start(ThreadFunc f, any_ptr v, char *name){  
  if(!th.ready_queue.empty()){
    cout<<"sorry thread already started now you have to create by t_fork:"<<endl;
  }
  else{
    th.ready_queue.push(th.context);
      if(!setjmp(th.context)){
        memcpy(th.lastContext,th.context,sizeof(jmp_buf));
        th.arg=v;
        th.func=f;

        //memcpy(th.currentThread->context,set_env,sizeof(jmp_buf));
        //cout<<"when jmp buf set then:"<<endl;
        th.ready_queue.push(th.context);
        th.func(th.arg);
      }
    //cout<<"after come back from long jump:"<<endl;
  }
}

void t_yield(){
  jmp_buf *j=(jmp_buf *)th.ready_queue.front();
  th.ready_queue.front()=th.context;

  longjmp(*j,2);
}

void t_fork(ThreadFunc f, any_ptr v, char *name){
  memcpy(th.lastContext,th.context,sizeof(jmp_buf));
  if(!setjmp(th.context)){
    f(v);
    th.ready_queue.push(th.context);
  }else
  {
  }
}//end of t_fork

void t_exit(int val){   
  cout<<"before long jump in t_exit"<<endl;
  jmp_buf *j=(jmp_buf *)th.ready_queue.front();
  th.ready_queue.pop();
  longjmp(*j,2);    
}

void block(){
  blocked_queue.push(th.context);
  jmp_buf *j=(jmp_buf *)th.ready_queue.front();
  th.ready_queue.pop();
  longjmp(*j,2);    
}

void unblock(){
  th.ready_queue.push(th.context);
  jmp_buf *j=(jmp_buf *)blocked_queue.front();
  blocked_queue.pop();
  longjmp(*j,2);    
}

我的测试用例是

#include<iostream>
#include<setjmp.h>
#include<stdio.h>
#include "threads.h"
#include<queue>

using namespace std;    
void fun2(any_ptr v){
  cout<<"in 2nd function:"<<endl;
  t_exit(0);
}

void helloworld(any_ptr v){
  cout<<"in hello world from start"<<endl;
  t_fork(fun2,NULL,"no value");
  cout<<"after second thread:"<<endl;
  cout<<"before exit"<<endl;
  t_exit(0);
}

void main(){
  cout<<"1 start"<<endl;
  t_start(helloworld, NULL, "my function");
  cout<<"main function"<<endl;
}//end of void main
4

3 回答 3

2

这是一个问题:

t_start函数中你这样做:

th.ready_queue.push(th.context);

ready_queue是一个指针队列,但th.context不是指针。

然后在t_yield你做的功能

jmp_buf *j=(jmp_buf *)th.ready_queue.front();

所以你推送非指针对象,并将它们作为指针弹出。如果您尝试将非指针对象作为指针访问,则您的行为未定义

你的代码,如果它编译没有错误,至少应该给你很多警告,如果你只收到一些警告,那么我建议你启用更多警告。当编译器给你一个警告时,它通常表明你做了一些你不应该做的事情,比如做一些会导致未定义行为的事情。仅仅通过类型转换来消除警告是一个非常糟糕的解决方案,因为它实际上并不能解决警告的原因。

此外,使用void*是坏代码即将到来的一个很好的迹象。如果可以避免,请不要使用它,在这种情况下,在您使用它的大多数地方(如 )中确实不需要它ready_queue

于 2014-04-29T07:32:58.297 回答
1

这段代码有几个问题,Joachim Pileborg 指出了其中一些问题。

另一个问题是你只有一个context,你多次使用它来存储不同的数据,但是当你回来时希望数据在那里。

解决方案是将您的ThreadSystem和您的Thread(线程的实际上下文)拆分为单独的对象:

struct Thread
{
    jmp_buf context;   // saved context of this thread
    void*    arg;
    ThreadFunc func; // function that this thread started executing
};

删除当前未使用的内容后,ThreadSystem如下所示:

struct ThreadSystem
{
    queue<Thread*> ready_queue;
};

线程创建/退出函数现在如下所示:

void t_start(ThreadFunc f, void* v)
{    
    if(!sys.ready_queue.empty()){
        cout<<"sorry thread already started now you have to create by t_fork:"<<endl;
    }
    else{
    Thread* th = new Thread;
        sys.ready_queue.push(th);


        if(!setjmp(th->context)){
            th->arg=v;
            th->func=f;

        cout << "&th->context=" << &th->context << endl;
            th->func(th->arg);
        }
    }
}

void t_fork(ThreadFunc f, void* v){
    Thread* th = new Thread; 
    th->func = f;
    th->arg = v;
    if(!setjmp(th->context))
    {
    cout << "&th->context=" << &th->context << endl;
        f(v);
        sys.ready_queue.push(th);
    }
}//end of t_fork

void t_exit(int val){   
    cout<<"before long jump in t_exit"<<endl;
    Thread* th=sys.ready_queue.front();
    sys.ready_queue.pop();
    // Memory leak here. We can't delete `th`, and still have a context.
    longjmp(th->context,2);  
}

但正如您所看到的,销毁线程存在问题 - 因此必须为此找到其他解决方案。我不确定这是一个很好的解决方案,但这有效(在执行发布的测试代码的有限程度上),原始代码没有。

于 2014-04-29T08:11:42.020 回答
0

好的。我的第一遍是不够的,因为我没有花足够的时间来理解原始代码。

代码有缺陷和混乱,但可能是可以修复的。当您将 th.context 推送到 ready_queue 时,您需要保存整个缓冲区,而不仅仅是缓冲区地址。可能还有很多其他问题。

更新 1

通过将 jmp_buf 包装在结构声明中,然后制作结构的 ready_queue 和 blocksed_queue 队列,解决了第一个问题。然后一个简单的分配将传输缓冲区内容。

struct SJBuff
{
    jmp_buf jb;
};

第二个问题:在 t_start() 中,不要在第一次初始化之前推送 th.context。

else
{
    // remove this line
    // th.readyQ.push(th.context);

    if(!setjmp(th.context.jb))
    {

结束更新 1

尽管如此,我真的不推荐使用setjmp(). 现代架构已经向前发展,仅仅保存几个寄存器并不能真正捕获足够的状态。想到优化编译器可能对您的代码做什么,我不寒而栗。流水线、条件执行、惰性求值、额外的寄存器、计划外的系统中断……

如果您专注于真正的目标,那么可能有更好的方法来做到这一点。

于 2014-04-29T06:57:45.863 回答