我在用户线程库上看到了一些问题,但似乎没有一个能回答我的问题。 我能够创建线程,运行它们,取消它们,然后退出它们。 由于某种原因,我无法做的是让一个线程返回数据。
初始化线程库时,我按如下方式设置退出线程上下文:
getcontext(&threadEnd);
threadEnd.uc_stack.ss_sp = (char *)malloc(SIGSTKSZ);
threadEnd.uc_stack.ss_size = SIGSTKSZ;
makecontext(&threadEnd, (void *) thread_exit, 1, &ReturnValue);
我创建一个线程并按如下方式分配它:
thread->myContext.uc_stack.ss_sp = (char *) malloc(SIGSTKSZ);
thread->myContext.uc_stack.ss_size = SIGSTKSZ;
thread->myContext.uc_link = &threadEnd;
当函数返回并调用 thread_exit() 时:
void thread_exit(void* retval){
int* test;
if (retval != NULL)
{
test = (int*) retval;
printf("Returned value: %in", *test);
fflush(stdout);
}
打印输出始终为"返回值:0"
被调用的函数返回指向整数的指针。
我做错了什么?
如果您在 GBD 中单步执行程序,则不会保存用于创建上下文的函数的返回。
我的实验示例:(观察 rax 寄存器):
返回语句:
thread1 (arg=0x1) at test_create_join.c:14
14 return (void *)11;
Value returned is $1 = 19
(gdb) info registers
rax 0x13 19
---
返回后:
(gdb) step
15 }
(gdb) info registers
rax 0xb 11
内部上下文切换:
__start_context () at ../sysdeps/unix/sysv/linux/x86_64/__start_context.S:32
32 ../sysdeps/unix/sysv/linux/x86_64/__start_context.S: No such file or directory.
(gdb) info registers
rax 0xb 11
您可以看到为几条指令保留了返回值,但经过几个步骤后,它变为 0。显然它特定于x86_64拱门,但我认为它可能与大多数拱门相同(即 makecontext 的行为)
现在,如果您需要线程函数的返回值,则可以采用另一种方式。只需创建一个线程处理程序来运行线程,并使用处理程序来创建新上下文。在这里,您可以获得要作为线程运行的函数的返回,并将其保存在线程控制块结构中以供以后使用。
typedef struct {
thread_start_routine start;
void *arg;
} entry_point;
static void thread_runner(void *args) {
exit_critical_section();
entry_point *entry = (entry_point *) args;
// run the thread and get the exit code
current->retcode = entry->start(entry->arg);
}
您需要一些简单的同步来等待一个线程。工作线程设置变量测试和信号。
// Pseudo code
// worker thread
retval = &VALUE; // retval is pointer?
SignalEvent( hEvent );
和主线程:
// Pseudo code
// main thread
int* test;
hEvent = CreateEvent();
WaitOnEvent( hEvent );
if (retval != NULL)
{
test = (int*) retval;
printf("Returned value: %in", *test);
fflush(stdout);
}
对于许多线程,您需要决定是简单地等待其中任何一个,还是等待任何但直到它们都发出信号,或者您想等到所有线程都发出信号然后检查值。此外,我们通常会考虑等待事件的线程是否在等待时执行一些空闲循环,并可能执行其他工作(更复杂的情况)。
如果您使用C++我可以提供一个带有 11 个条件变量的示例C++但对于 C,它通常是 POSIX 或 Win32 API,但我们可以使用伪代码。Win32 SetEvent 和 WaitForSingleObject。这个 POSIX 主题几乎涵盖了您的条件变量。
请注意,线程完成一个线程也会触发信号,因此在 Win32 中,您可以直接等待线程句柄,但这种方法可能不可移植。此外,线程及其同步不是 C 语言标准的一部分,因此只适用于提供两者的某些操作系统才有意义。
首先,当向makecontext
提供参数时,最后一个应该始终NULL
:
makecontext(&threadEnd, (void *) thread_exit, 1, &ReturnValue, NULL);
我遇到了同样的问题,我处理它的方式有点不同,即不使用我存储start_routine
uc_link
,即 func
和arg
thread
结构内部并使用了包装函数:实际调用线程函数并存储返回值的thread_runner
:
makecontext(&thread->ctx, (void *) thread_runner, 1, thread, NULL);
其中线程流道是:
void thread_runner(thread_t *thread){
void **retval = thread->func(thread->arg);
if(retval != NULL){
thread_exit(*retval);
} else {
thread_exit(NULL);
}
}
我们不应该在 makecontext() 的参数列表中传递指针。它们必须是手册中指定的整数:
在某些体系结构上,函数 func 被调用,并传递了序列遵循 argc 的整数 (int) 参数;调用方必须在 argc 中指定这些参数的数量。
整数与指针具有相同的大小(例如 32 位),但在其他体系结构上,指针为 64 位,而整数的长度为 32 位。在最近的 GLIBC/Linux x86_64 体系结构中,参数在上下文中存储为"长长"整数。因此,这可能会起作用,因为这会使参数存储为 64 位值(与指针的大小兼容),但这不是可移植的。因此,这可以解释为什么您没有通过"&ReturnValue"指针获得正确的值。