我正在编写一个代码,其中有两个并行运行的线程。
第一个是启动第二个线程的主线程。第二个线程只是一个执行空while循环的简单线程。
现在我想暂停/暂停创建它的第一个线程执行第二个线程。一段时间后,我想从暂停/暂停的位置恢复执行第二个线程(通过发出一些命令或函数)。
我正在编写一个代码,其中有两个并行运行的线程。
第一个是启动第二个线程的主线程。第二个线程只是一个执行空while循环的简单线程。
现在我想暂停/暂停创建它的第一个线程执行第二个线程。一段时间后,我想从暂停/暂停的位置恢复执行第二个线程(通过发出一些命令或函数)。
这个问题不是关于如何使用互斥锁,而是如何挂起线程。
在 Unix 规范中有一个叫 pthread_suspend 的线程函数,还有一个叫 pthread_resume_np 的线程函数,但由于某种原因,制作 Linux、FreeBSD、NetBSD 等的人并没有实现这些函数。
所以要理解它,功能根本不存在。有一些解决方法,但不幸的是它与在 Windows 上调用 SuspendThread 不同。您必须做各种不可移植的事情才能使线程停止并开始使用信号。
停止和恢复线程对于调试器和垃圾收集器至关重要。例如,我见过一个无法正确实现“SuspendThread”功能的 Wine 版本。因此,任何使用它的 Windows 程序都无法正常工作。
我认为可以根据 JVM 为垃圾收集器使用这种信号技术这一事实正确地使用信号来做到这一点,但我也刚刚在网上看到一些文章,人们注意到 JVM 出现死锁等问题,有时不可重现。
所以为了回答这个问题,除非你有一个很好的 Unix 来实现 pthread_suspend_np,否则你不能用 Unix 正确地挂起和恢复线程。否则你会被信号卡住。
Signals 的最大问题是当你有大约五个不同的库都链接到同一个程序并且都试图同时使用相同的信号时。出于这个原因,我相信您实际上不能在一个程序中使用 ValGrind 之类的东西,例如 Boehm GC。至少在用户空间的最低级别没有主要编码。
这个问题的另一个答案可能是。像 Linuz Torvalds 对 NVidia 所做的那样,向他指指点点,让他实现 Linux 中缺少的两个最关键的部分。首先,pthread_suspend,其次,内存页面上的脏位,以便可以实现适当的垃圾收集器。在网上发起一个大型请愿书,并不断地动动手指。也许到 Windows 20 出来的时候,他们会意识到暂停和恢复线程,还有脏位实际上是 Windows 和 Mac 优于 Linux 或任何不实现 pthread_suspend 的 Unix 的根本原因之一,也是脏位在虚拟页面上,就像在 Windows 中的 VirtualAlloc 一样。
我不活在希望中。实际上,对我来说,我花了数年时间围绕为 Linux 构建东西来规划我的未来,但放弃了希望作为可靠的东西,这一切似乎都取决于虚拟内存的脏位的可用性,以及干净地挂起线程。
据我所知,你不能真的只是使用 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;
}
POSIX 中没有 pthread_suspend()、pthread_resume() 类型的 API。
大多数条件变量可用于控制其他线程的执行。
条件变量机制允许线程暂停执行并放弃处理器,直到某个条件为真。条件变量必须始终与互斥锁相关联,以避免由一个准备等待的线程和另一个线程创建的竞争条件,该线程可能在第一个线程实际等待它之前发出条件信号,从而导致死锁。
欲了解更多信息
如果您可以改用进程,则可以将作业控制信号(SIGSTOP / SIGCONT)发送到第二个进程。如果您仍想在这些进程之间共享内存,您可以使用 SysV 共享内存(shmop、shmget、shmctl...)。
即使我自己没有尝试过,也可以使用较低级别的 clone() 系统调用来生成不共享信号的线程。有了它,您也许可以将 SIGSTOP 和 SIGCONT 发送到另一个线程。
不知道你是否会喜欢我的回答。但是您可以通过这种方式实现它。
如果它是一个单独的进程而不是线程,我有一个使用信号的解决方案(这甚至可能适用于线程,也许有人可以分享你的想法)。
当前没有系统可以暂停或恢复进程的执行。但你肯定可以建造一个。
如果我想在我的项目中使用它,我会做的步骤:
如果您可以实现这一点,请分享您的反馈,无论它是否适合您。谢谢。
您可以使用互斥锁来做到这一点,伪代码将是:
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 */
}
为了在线程上实现暂停,您需要让它等待某个事件发生。等待自旋锁互斥体是在浪费 CPU 周期。恕我直言,不应遵循此方法,因为 CPU 周期可能已被其他进程/线程用完。等待非阻塞描述符(管道、套接字或其他)。使用管道进行线程间通信的示例代码可以在这里看到上述解决方案很有用,如果您的第二个线程具有来自多个来源的更多信息,而不仅仅是暂停和恢复信号。顶级 select/poll/epoll 可用于非阻塞描述符。您可以指定 select/poll/epoll 系统调用的等待时间,只会浪费那么多微秒的 CPU 周期。我以前瞻性的方式提到了这个解决方案,即您的第二个线程将处理更多的事情或事件,而不仅仅是暂停和恢复。对不起,如果它比你问的更详细。
另一种更简单的方法是在这些线程之间有一个共享的布尔变量。主线程是变量的写入者,0 - 表示停止。1 - 表示恢复第二个线程只读取变量的值。要实现“0”状态,请使用 usleep 几微秒,然后再次检查该值。假设您的设计可以接受几微秒的延迟。要实现'1' - 在执行一定数量的操作后检查变量的值。否则,您还可以实现从“1”状态移动到“0”状态的信号。
您可以简单地通过信号暂停线程
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);
}
}