Я пытаюсь реализовать простую пользовательскую библиотеку потоков на уровне 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
Есть несколько проблем с этим кодом, на некоторые из которых указывает Иоахим Пилеборг.
Другая проблема в том, что у вас есть только один 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);
}
Но, как вы можете видеть, существует проблема в уничтожении потока — поэтому для этого нужно будет найти другое решение. Я не уверен, что это отличное решение, но оно работает (до ограниченной степени выполнения отправленного тестового кода), где исходный код этого не сделал.
Вот один проблема:
в 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
).
ХОРОШО. Мой первый проход был неадекватным, так как я не тратил достаточно времени на понимание оригинального кода.
Код глючит и грязен, но, вероятно, исправим. Когда вы помещаете 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()
, Современные архитектуры пошли дальше, и просто сохранение нескольких регистров на самом деле не фиксирует достаточно состояния. Мне страшно подумать, что оптимизирующий компилятор может сделать с вашим кодом. Пиплайн, условное выполнение, отложенная оценка, дополнительные регистры, незапланированные системные прерывания, …
Если вы сосредоточены на своих реальных целях, возможно, есть лучший способ сделать это.