【发布时间】:2012-07-13 04:15:06
【问题描述】:
我正在编写一个代码,其中有两个并行运行的线程。
1st 是启动第二个线程的主线程。 第二个线程只是一个执行空while循环的简单线程。
现在我想暂停/暂停创建它的第一个线程执行第二个线程。 一段时间后,我想从暂停/暂停的位置恢复第二个线程的执行(通过发出一些命令或函数)。
【问题讨论】:
标签: c linux multithreading pthreads
我正在编写一个代码,其中有两个并行运行的线程。
1st 是启动第二个线程的主线程。 第二个线程只是一个执行空while循环的简单线程。
现在我想暂停/暂停创建它的第一个线程执行第二个线程。 一段时间后,我想从暂停/暂停的位置恢复第二个线程的执行(通过发出一些命令或函数)。
【问题讨论】:
标签: c linux multithreading pthreads
不确定你是否会喜欢我的回答。但是您可以通过这种方式实现。
如果它是一个单独的进程而不是线程,我有一个解决方案(这甚至可能适用于线程,也许有人可以分享你的想法)使用信号。
目前没有系统可以暂停或恢复进程的执行。 但你肯定可以建造一个。
如果我想在我的项目中使用它,我会采取的步骤:
如果您可以实现这一点,请分享您的反馈,无论它是否适合您。谢谢。
【讨论】:
我尝试使用信号暂停和恢复线程,这是我的解决方案。请用-pthread编译链接。
信号 SIGUSR1 通过调用 pause() 挂起线程,SIGUSR2 恢复线程。
来自暂停的手册页:
pause() 导致调用进程(或线程)休眠,直到 发出的信号要么终止进程,要么导致 调用一个 信号捕捉功能。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
// Since I have only 2 threads so using two variables,
// array of bools will be more useful for `n` number of threads.
static int is_th1_ready = 0;
static int is_th2_ready = 0;
static void cb_sig(int signal)
{
switch(signal) {
case SIGUSR1:
pause();
break;
case SIGUSR2:
break;
}
}
static void *thread_job(void *t_id)
{
int i = 0;
struct sigaction act;
pthread_detach(pthread_self());
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = cb_sig;
if (sigaction(SIGUSR1, &act, NULL) == -1)
printf("unable to handle siguser1\n");
if (sigaction(SIGUSR2, &act, NULL) == -1)
printf("unable to handle siguser2\n");
if (t_id == (void *)1)
is_th1_ready = 1;
if (t_id == (void *)2)
is_th2_ready = 1;
while (1) {
printf("thread id: %p, counter: %d\n", t_id, i++);
sleep(1);
}
return NULL;
}
int main()
{
int terminate = 0;
int user_input;
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, thread_job, (void *)1);
// Spawned thread2 just to make sure it isn't suspended/paused
// when thread1 received SIGUSR1/SIGUSR2 signal
pthread_create(&thread2, NULL, thread_job, (void *)2);
while (!is_th1_ready && !is_th2_ready);
while (!terminate) {
// to test, I am sensing signals depending on input from STDIN
printf("0: pause thread1, 1: resume thread1, -1: exit\n");
scanf("%d", &user_input);
switch(user_input) {
case -1:
printf("terminating\n");
terminate = 1;
break;
case 0:
printf("raising SIGUSR1 to thread1\n");
pthread_kill(thread1, SIGUSR1);
break;
case 1:
printf("raising SIGUSR2 to thread1\n");
pthread_kill(thread1, SIGUSR2);
break;
}
}
pthread_kill(thread1, SIGKILL);
pthread_kill(thread2, SIGKILL);
return 0;
}
【讨论】:
scanf 从实际的人类 bean 中读取,输入图形终端,这可能无关紧要。但是一旦有人用重定向的标准输入运行这个程序,你可能会在发送 SIGUSR1 什么都不做的情况下运行(因为一个线程还没有安装它的信号处理程序)。更重要的是,您的程序有时可能会死锁,因为 printf 不是异步信号安全的,并且您是从信号处理程序调用它。
printf,您也会发现,暂停 thread1 有时会神秘地暂停 thread2 甚至锁定您的整个程序。这是因为printf 不是异步信号安全的——它需要许多锁(堆锁、stdio 锁),并且在对printf 的调用返回之前不会释放它们。当信号到达 在 printf 释放它的锁之前,你的整个程序实际上不能调用printf/scanf/malloc 等:他们使用的锁被现在挂起的线程和将阻止调用者,直到被挂起者释放它们(所以永远不会)。
printf 仅用于调试,因此可以将其删除。我会确保线程在执行任何操作之前准备好。 @user1643723 在您的第二条评论中,第一行和第二行相互矛盾,您说在第一行中删除 printf,在第二行中您说问题是因为在信号处理程序中有 printf。你能详细说明一下吗?
scanf 只是为了测试我的代码,最终这应该是事件驱动的,如果出现某些情况可能会暂停线程或恢复它。我认为scanf 是测试它的最简单方法。 @user1643723,如果您对测试代码有任何建议,请告诉我。
你可以简单地通过信号挂起一个线程
pthread_mutex_t mutex;
static void thread_control_handler(int n, siginfo_t* siginfo, void* sigcontext) {
// wait time out
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
}
// suspend a thread for some time
void thread_suspend(int tid, int time) {
struct sigaction act;
struct sigaction oact;
memset(&act, 0, sizeof(act));
act.sa_sigaction = thread_control_handler;
act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
sigemptyset(&act.sa_mask);
pthread_mutex_init(&mutex, 0);
if (!sigaction(SIGURG, &act, &oact)) {
pthread_mutex_lock(&mutex);
kill(tid, SIGURG);
sleep(time);
pthread_mutex_unlock(&mutex);
}
}
【讨论】:
这个问题不是关于如何使用互斥锁,而是如何挂起一个线程。
在 Unix 规范中有一个线程函数叫做 pthread_suspend,另一个叫做 pthread_resume_np,但是由于某种原因,制作 Linux、FreeBSD、NetBSD 等的人没有实现这些函数。
所以要理解它,功能根本不存在。有一些解决方法,但不幸的是它与在 Windows 上调用 SuspendThread 不同。你必须做各种不可移植的事情才能让线程停止并开始使用信号。
停止和恢复线程对于调试器和垃圾收集器至关重要。例如,我见过一个无法正确实现“SuspendThread”功能的 Wine 版本。因此任何使用它的 Windows 程序都无法正常工作。
我认为基于 JVM 将这种信号技术用于垃圾收集器这一事实,可以正确使用信号来执行此操作,但我也刚刚在网上看到一些文章,其中人们注意到死锁等等JVM,有时无法重现。
所以为了回答这个问题,除非你有一个很好的 Unix 来实现 pthread_suspend_np,否则你不能正确地挂起和恢复 Unix 的线程。否则你会被信号卡住。
信号的最大问题是当您有大约五个不同的库都链接到同一个程序并且都试图同时使用相同的信号时。出于这个原因,我相信您实际上不能在一个程序中使用 ValGrind 之类的东西,例如 Boehm GC。至少在用户空间的最低级别没有主要编码。
这个问题的另一个答案可能是。像 Linuz Torvalds 对 NVidia 所做的那样,向他指指点点,让他实现 Linux 中缺少的两个最关键的部分。首先,pthread_suspend,其次,内存页面上的脏位,以便可以实现适当的垃圾收集器。在网上发起一个大型请愿书,并不断地动动手指。也许到 Windows 20 出来的时候,他们会意识到暂停和恢复线程,还有脏位实际上是 Windows 和 Mac 优于 Linux 或任何不实现 pthread_suspend 的 Unix 的根本原因之一,也是脏位在虚拟页面上,就像在 Windows 中的 VirtualAlloc 一样。
我没有希望。实际上,对我来说,我花了数年时间围绕为 Linux 构建东西来规划我的未来,但放弃了希望作为可靠的东西,这一切似乎都取决于虚拟内存的脏位的可用性,以及干净地挂起线程。
【讨论】:
为了在线程上实现暂停,您需要让它等待某个事件发生。等待自旋锁互斥体是在浪费 CPU 周期。恕我直言,不应遵循此方法,因为 CPU 周期可能已被其他进程/线程用完。 等待非阻塞描述符(管道、套接字或其他)。可以在这里看到使用pipes for inter-thread communication 的示例代码 如果您的第二个线程具有来自多个来源的更多信息,而不仅仅是暂停和恢复信号,则上述解决方案很有用。顶级 select/poll/epoll 可用于非阻塞描述符。您可以指定 select/poll/epoll 系统调用的等待时间,只会浪费那么多微秒的 CPU 周期。 我以前瞻性的方式提到了这个解决方案,即您的第二个线程将处理更多的事情或事件,而不仅仅是暂停和恢复。对不起,如果它比你问的更详细。
另一种更简单的方法是在这些线程之间共享一个布尔变量。 主线程是变量的写入者,0 - 表示停止。 1 - 表示简历 第二个线程只读取变量的值。要实现“0”状态,请使用 usleep 几微秒,然后再次检查该值。假设您的设计可以接受几微秒的延迟。 要实现'1' - 在执行一定数量的操作后检查变量的值。 否则,您还可以实现从“1”状态移动到“0”状态的信号。
【讨论】:
如果您可以改用进程,则可以将作业控制信号 (SIGSTOP / SIGCONT) 发送到第二个进程。如果您仍想在这些进程之间共享内存,可以使用 SysV 共享内存(shmop、shmget、shmctl...)。
即使我自己没有尝试过,也可以使用较低级别的 clone() 系统调用来生成不共享信号的线程。有了它,您也许可以将 SIGSTOP 和 SIGCONT 发送到另一个线程。
【讨论】:
POSIX 中没有 pthread_suspend()、pthread_resume() 类型的 API。
大多数条件变量可用于控制其他线程的执行。
条件变量机制允许线程暂停执行 并放弃处理器,直到某些条件为真。一个条件 变量必须始终与互斥锁相关联以避免竞争 由一个准备等待的线程和另一个线程创建的条件 这可能会在第一个线程实际等待之前发出条件信号 导致死锁。
更多信息
【讨论】:
您可以使用互斥锁来做到这一点,伪代码是:
While (true) {
/* pause resume */
lock(my_lock); /* if this is locked by thread1, thread2 will wait until thread1 */
/* unlocks it */
unlock(my_lock); /* unlock so that next iteration thread2 could lock */
/* do actual work here */
}
【讨论】:
据我所知,您不能真正使用 pthread 暂停其他线程。你必须在你的第二个线程中有一些东西来检查它应该使用条件变量之类的东西暂停的时间。这是做这类事情的标准方法。
【讨论】:
pthread_kill(...SIGSTOP),虽然没有明确的支持,这将做到这一点。但我强烈建议不要这样做。挂起一个线程会停止它的执行,无论它在哪里,无论它在做什么(例如它可能持有一个锁,或者有一半完成的数据)。在 condvar/semaphore 上的阻塞以一种受控的方式在众所周知的时间点以众所周知的状态停止执行。没有惊喜,没有未知的副作用。