gpt4 book ai didi

c - 疯狂的宏hack,用于处理线程取消和清理处理程序的问题

转载 作者:行者123 更新时间:2023-11-30 16:43:17 25 4
gpt4 key购买 nike

由于代码段和详细说明,这是一个非常长的问题。 TL; DR,下面显示的宏是否有问题,这是否是一个合理的解决方案,如果不是,那么解决以下问题的最合理方法是什么?

我目前正在编写一个处理POSIX线程的C库,并且必须能够干净地处理线程取消。特别是,可以从用户设置为可取消(PTHREAD_CANCEL_DEFFEREDPTHREAD_CANCEL_ASYNCHRONOUS取消类型)的线程中调用库函数。

当前,与用户交互的库函数都以调用pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate)开始,在每个返回点,我都确保调用pthread_setcancelstate(oldstate, &dummy)以恢复线程先前具有的所有取消设置。

这基本上可以防止线程在库代码中被取消,从而确保全局状态保持一致,并且在返回之前对资源进行了适当的管理。

不幸的是,此方法有一些缺点:


必须确保在每个返回点都恢复取消状态。如果该函数具有带有多个返回点的非平凡的控制流,这将使管理变得有些困难。忘记这样做可能导致即使从库返回后线程也不会被取消。
我们只需要在分配资源或全局状态不一致的地方防止取消。库函数可以依次调用其他可以安全取消的内部库函数,并且理想情况下可以在这些点处进行取消。


这是问题的示例说明:

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>

static void do_some_long_computation(char *buffer, size_t len)
{
(void)buffer; (void)len;
/* This is really, really long! */
}

int mylib_function(size_t len)
{
char *buffer;
int oldstate, oldstate2;

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);

buffer = malloc(len);

if (buffer == NULL) {
pthread_setcancelstate(oldstate, &oldstate2);
return -1;
}

do_some_long_computation(buffer, len);

fd = open("results.txt", O_WRONLY);

if (fd < 0) {
free(buffer);
pthread_setcancelstate(oldstate, &oldstate2);
return -1;
}

write(fd, buffer, len); /* Normally also do error-check */
close(fd);

free(buffer);

pthread_setcancelstate(oldstate, &oldstate2);

return 0;
}


在这里还不错,因为只有3个返回点。甚至可能以某种方式重构控制流,以强制所有路径都到达单个返回点,也许使用 goto cleanup模式。但是,第二个问题仍然没有解决。并想象必须对许多库函数执行此操作。

第二个问题可以通过使用 pthread_setcancelstate调用包装每个资源分配来解决,这将仅禁用资源分配期间的取消。禁用取消后,我们还会推送一个清理处理程序(使用 pthread_cleanup_push)。也可以将所有资源分配一起移动(在进行长时间计算之前打开文件)。

解决第二个问题时,仍然很难维护,因为每个资源分配都需要包装在这些 pthread_setcancelstatepthread_cleanup_[push|pop]调用下。同样,并非总是可能将所有资源分配放在一起,例如,如果它们取决于计算结果。此外,由于不能在 pthread_cleanup_pushpthread_cleanup_pop对之间返回(例如,如果 malloc返回 NULL的情况),则需要更改控制流。

为了解决这两个问题,我想出了另一种可能的方法,该方法涉及使用宏进行肮脏的hack。这个想法是模拟其他语言中的关键部分块之类的东西,在“取消安全”范围内插入代码块。

库代码如下所示(使用 -c -Wall -Wextra -pedantic编译):

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>

#include "cancelsafe.h"

static void do_some_long_computation(char *buffer, size_t len)
{
(void)buffer; (void)len;
/* This is really, really long! */
}

static void free_wrapper(void *arg)
{
free(*(void **)arg);
}

static void close_wrapper(void *arg)
{
close(*(int *)arg);
}

int mylib_function(size_t len)
{
char *buffer;
int fd;
int rc;

rc = 0;
CANCELSAFE_INIT();

CANCELSAFE_PUSH(free_wrapper, buffer) {
buffer = malloc(len);

if (buffer == NULL) {
rc = -1;
CANCELSAFE_BREAK(buffer);
}
}

do_some_long_computation(buffer, len);

CANCELSAFE_PUSH(close_wrapper, fd) {
fd = open("results.txt", O_WRONLY);

if (fd < 0) {
rc = -1;
CANCELSAFE_BREAK(fd);
}
}

write(fd, buffer, len);

CANCELSAFE_POP(fd, 1); /* close fd */
CANCELSAFE_POP(buffer, 1); /* free buffer */

CANCELSAFE_END();

return rc;
}


这在一定程度上解决了这两个问题。 cancelstate设置和清理push / pop调用在宏中是隐式的,因此程序员只需要指定需要取消安全的代码段以及要推送的清理处理程序即可。其余操作在后台完成,编译器将确保每个 CANCELSAFE_PUSH与一个 CANCELSAFE_POP配对。

宏的实现如下:

#define CANCELSAFE_INIT() \
do {\
int CANCELSAFE_global_stop = 0

#define CANCELSAFE_PUSH(cleanup, ident) \
do {\
int CANCELSAFE_oldstate_##ident, CANCELSAFE_oldstate2_##ident;\
int CANCELSAFE_stop_##ident;\
\
if (CANCELSAFE_global_stop)\
break;\
\
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_##ident);\
pthread_cleanup_push(cleanup, &ident);\
for (CANCELSAFE_stop_##ident = 0; CANCELSAFE_stop_##ident == 0 && CANCELSAFE_global_stop == 0; CANCELSAFE_stop_##ident = 1, pthread_setcancelstate(CANCELSAFE_oldstate_##ident, &CANCELSAFE_oldstate2_##ident))

#define CANCELSAFE_BREAK(ident) \
do {\
CANCELSAFE_global_stop = 1;\
pthread_setcancelstate(CANCELSAFE_oldstate_##ident, &CANCELSAFE_oldstate2_##ident);\
goto CANCELSAFE_POP_LABEL_##ident;\
} while (0)

#define CANCELSAFE_POP(ident, execute) \
CANCELSAFE_POP_LABEL_##ident:\
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_##ident);\
pthread_cleanup_pop(execute);\
pthread_setcancelstate(CANCELSAFE_oldstate_##ident, &CANCELSAFE_oldstate2_##ident);\
} while (0)

#define CANCELSAFE_END() \
} while (0)


这结合了我之前遇到的一些宏技巧。

do { } while (0)模式用于具有类似于多行函数的宏(需要使用分号)。

通过使用与 CANCELSAFE_PUSHCANCELSAFE_POP相同的技巧,分别使用不匹配的 pthread_cleanup_pushpthread_cleanup_pop括号,将 {}宏强制成对使用(此处是不匹配的 do {} while (0)代替)。

for循环的用法在某种程度上受此 question的启发。这个想法是我们想在宏主体之后调用 pthread_setcancelstate函数,以在CANCELSAFE_PUSH块之后恢复取消。我使用在第二次循环迭代时设置为1的停止标志。

ident是将要释放的变量的名称(必须是有效的标识符)。将为cleanup_wrappers提供其地址,该地址根据此 answer在清理处理程序范围中始终有效。这样做是因为变量的值在清除推送时尚未初始化(如果变量不是指针类型,则也不起作用)。

通过将ident作为后缀附加到 ##串联宏中,为它们赋予唯一的名称,该ident还用于避免临时变量和标签中的名称冲突。

CANCELSAFE_BREAK宏用于跳出cancelsafe块并直接进入相应的 CANCELSAFE_POP_LABEL。如 here所述,这受 goto cleanup模式的启发。它还设置全局停止标志。

全局停止用于避免在相同作用域级别上可能存在两个PUSH / POP对的情况。这似乎是一种不太可能的情况,但是如果发生这种情况,则在将全局停止标志设置为1时,基本上会跳过宏的内容。 CANCELSAFE_INITCANCELSAFE_END宏并不重要,它们只是避免了需要自己声明全局停止标志。如果程序员始终执行所有推入操作,然后连续执行所有弹出操作,则可以跳过这些操作。

扩展宏之后,我们为mylib_function获取以下代码:

int mylib_function(size_t len)
{
char *buffer;
int fd;
int rc;

rc = 0;
do {
int CANCELSAFE_global_stop = 0;

do {
int CANCELSAFE_oldstate_buffer, CANCELSAFE_oldstate2_buffer;
int CANCELSAFE_stop_buffer;

if (CANCELSAFE_global_stop)
break;

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_buffer);
pthread_cleanup_push(free_wrapper, &buffer);
for (CANCELSAFE_stop_buffer = 0; CANCELSAFE_stop_buffer == 0 && CANCELSAFE_global_stop == 0; CANCELSAFE_stop_buffer = 1, pthread_setcancelstate(CANCELSAFE_oldstate_buffer, &CANCELSAFE_oldstate2_buffer)) {
buffer = malloc(len);

if (buffer == NULL) {
rc = -1;
do {
CANCELSAFE_global_stop = 1;
pthread_setcancelstate(CANCELSAFE_oldstate_buffer, &CANCELSAFE_oldstate2_buffer);
goto CANCELSAFE_POP_LABEL_buffer;
} while (0);
}
}

do_some_long_computation(buffer, len);

do {
int CANCELSAFE_oldstate_fd, CANCELSAFE_oldstate2_fd;
int CANCELSAFE_stop_fd;

if (CANCELSAFE_global_stop)
break;

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_fd);
pthread_cleanup_push(close_wrapper, &fd);
for (CANCELSAFE_stop_fd = 0; CANCELSAFE_stop_fd == 0 && CANCELSAFE_global_stop == 0; CANCELSAFE_stop_fd = 1, pthread_setcancelstate(CANCELSAFE_oldstate_fd, &CANCELSTATE_oldstate2_fd)) {
fd = open("results.txt", O_WRONLY);

if (fd < 0) {
rc = -1;
do {
CANCELSAFE_global_stop = 1;
pthread_setcancelstate(CANCELSAFE_oldstate_fd, &CANCELSAFE_oldstate2_fd);
goto CANCELSAFE_POP_LABEL_fd;
} while (0);
}
}

write(fd, buffer, len);

CANCELSAFE_POP_LABEL_fd:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_fd);
pthread_cleanup_pop(1);
pthread_setcancelstate(CANCELSAFE_oldstate_fd, &CANCELSAFE_oldstate2_fd);
} while (0);

CANCELSAFE_POP_LABEL_buffer:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_buffer);
pthread_cleanup_pop(1);
pthread_setcancelstate(CANCELSAFE_oldstate_buffer, &CANCELSAFE_oldstate2_buffer);
} while (0);
} while (0);

return rc;
}


现在,看这组宏太可怕了,要了解它们的工作原理有些棘手。另一方面,这是一项一次性的任务,一旦编写,就可以保留它们,而项目的其余部分则可以从其良好的收益中受益。

我想知道宏是否存在我可能忽略的问题,以及是否有更好的方法来实现类似的功能。另外,您认为哪种建议的解决方案最合理?还有其他更好的想法可以更好地解决这些问题(或者,它们真的不是问题)吗?

最佳答案

除非使用异步取消(这总是很麻烦),否则不必禁用mallocfree(以及许多其他POSIX函数)周围的取消。同步取消仅在取消点发生,而这些功能则没有。

您正在滥用POSIX取消处理工具来实现范围出口挂钩。通常,如果您发现自己在C中执行此类操作,则应认真考虑使用C ++。这将为您提供该功能的更完善的版本,并带有充足的文档,并且程序员已经对此有所了解。

关于c - 疯狂的宏hack,用于处理线程取消和清理处理程序的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45386442/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com