Windows — использование set_jmp / longjmp в C ++ не работает

Я пытаюсь реализовать простую пользовательскую библиотеку потоков на уровне c.when, когда один поток запускается, и этот поток вызывает второй поток. этот второй поток работает правильно, но при выходе из программы происходит сбой. Вот моя кодировка.

//**********************************************
#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

0

Решение

Есть несколько проблем с этим кодом, на некоторые из которых указывает Иоахим Пилеборг.

Другая проблема в том, что у вас есть только один 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);
}

Но, как вы можете видеть, существует проблема в уничтожении потока — поэтому для этого нужно будет найти другое решение. Я не уверен, что это отличное решение, но оно работает (до ограниченной степени выполнения отправленного тестового кода), где исходный код этого не сделал.

1

Другие решения

Вот один проблема:

в 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).

2

ХОРОШО. Мой первый проход был неадекватным, так как я не тратил достаточно времени на понимание оригинального кода.

Код глючит и грязен, но, вероятно, исправим. Когда вы помещаете th.context в ready_queue, вам нужно сохранить весь буфер, а не только адрес буфера. Вероятно, много других проблем.

Обновление 1

Решил первую проблему, обернув jmp_buf в объявление структуры, а затем создавая очереди готовых структур и блокировки_объектов. Затем простое назначение передаст содержимое буфера.

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(), Современные архитектуры пошли дальше, и просто сохранение нескольких регистров на самом деле не фиксирует достаточно состояния. Мне страшно подумать, что оптимизирующий компилятор может сделать с вашим кодом. Пиплайн, условное выполнение, отложенная оценка, дополнительные регистры, незапланированные системные прерывания, …

Если вы сосредоточены на своих реальных целях, возможно, есть лучший способ сделать это.

0
По вопросам рекламы [email protected]