最近,我开始将ucos-ii移植到Ubuntu PC上。
正如我们所知道的,通过简单地在pthread的回调函数的"while“循环中添加一个标志来执行暂停和恢复(就像下面的解决方案一样),不可能在ucos-ii中模拟”进程“。因为ucos-ii中的“进程”可以在任何时候暂停或恢复!
How to sleep or pause a PThread in c on Linux
我在下面的网站上找到了一个解决方案,但它不能构建,因为它是过时的。它使用Linux中的进程来模拟ucos-ii中的任务(行为类似于我们的Linux中的进程)。
http://www2.hs-esslingen.de/~zimmerma/software/index_uk.html
如果pthread可以像进程一样,可以随时暂停和恢复,请告诉我一些相关的函数,我可以自己弄明白。如果不能,我认为我应该专注于较旧的解决方案。非常感谢。
发布于 2012-02-23 14:54:20
如果使用条件变量在特定点停止是不够的,那么您不能使用pthread来做到这一点。pthread接口不包括挂起/恢复功能。
例如,请参见答案E.4 here
POSIX标准没有提供线程A可以在没有来自B的合作的情况下挂起另一个线程B的执行的机制。实现挂起/重启机制的唯一方法是让B周期性地检查挂起请求的某个全局变量,然后在条件变量上挂起自己,另一个线程可以稍后用信号通知该条件变量以重启B。
常见问题解答接着描述了两种非标准的方法,一种是在Solaris中,另一种是在LinuxThreads中(它现在已经过时了;不要将它与Linux上的当前线程混淆);这两种方法都不适用于您的情况。
发布于 2014-08-17 00:55:25
Modula 3垃圾收集器需要在任意时间挂起pthread,而不仅仅是在它们等待条件变量或互斥时。它通过注册一个(Unix)信号处理程序来挂起线程,然后使用pthread_kill向目标线程发送信号。我认为它可以工作(它对其他人来说是可靠的,但我现在正在调试它的一个问题……)不过,这有点笨拙...
在谷歌上搜索ThreadPThread.m3,看看例程"StopWorld“和"StartWorld”。处理程序本身在ThreadPThreadC.c中。
发布于 2021-06-24 23:59:33
在Linux上,您可能可以设置自定义信号处理程序(例如,使用信号()),它将包含等待另一个信号(例如,使用sigsuspend())。然后使用pthread_kill()或tgkill()发送信号。为此,使用所谓的“实时信号”很重要,因为像SIGUSR1和SIGUSR2这样的正常信号不会排队,这意味着它们可能在高负载条件下丢失。您多次发送一个信号,但它只接收到一次,因为以前在信号处理程序运行时,同类的新信号会被忽略。因此,如果你有并发的线程执行暂停/恢复,你可能会松散恢复事件并导致死锁。另一方面,未对挂起的实时信号(如SIGRTMIN+1和SIGRTMIN+2)进行重复数据删除,因此队列中可能同时存在多个相同的rt信号。
免责声明:我还没有尝试过。但从理论上讲,它应该是可行的。
另请参阅man 7信号安全。有一个可以在信号处理程序中安全调用的调用列表。幸运的是,sigsuspend()似乎就是其中之一。
更新:我这里有可用的代码:
//Filename: pthread_pause.c
//Author: Tomas 'Harvie' Mudrunka 2021
//Build: CFLAGS=-lpthread make pthread_pause; ./pthread_pause
//Test: valgrind --tool=helgrind ./pthread_pause
//I've wrote this code as excercise to solve following stack overflow question:
// https://stackoverflow.com/questions/9397068/how-to-pause-a-pthread-any-time-i-want/68119116#68119116
#define _GNU_SOURCE //pthread_yield() needs this
#include <signal.h>
#include <pthread.h>
//#include <pthread_extra.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <sys/resource.h>
#include <time.h>
#define PTHREAD_XSIG_STOP (SIGRTMIN+0)
#define PTHREAD_XSIG_CONT (SIGRTMIN+1)
#define PTHREAD_XSIGRTMIN (SIGRTMIN+2) //First unused RT signal
pthread_t main_thread;
sem_t pthread_pause_sem;
pthread_once_t pthread_pause_once_ctrl = PTHREAD_ONCE_INIT;
void pthread_pause_once(void) {
sem_init(&pthread_pause_sem, 0, 1);
}
#define pthread_pause_init() (pthread_once(&pthread_pause_once_ctrl, &pthread_pause_once))
#define NSEC_PER_SEC (1000*1000*1000)
// timespec_normalise() from https://github.com/solemnwarning/timespec/
struct timespec timespec_normalise(struct timespec ts)
{
while(ts.tv_nsec >= NSEC_PER_SEC) {
++(ts.tv_sec); ts.tv_nsec -= NSEC_PER_SEC;
}
while(ts.tv_nsec <= -NSEC_PER_SEC) {
--(ts.tv_sec); ts.tv_nsec += NSEC_PER_SEC;
}
if(ts.tv_nsec < 0) { // Negative nanoseconds isn't valid according to POSIX.
--(ts.tv_sec); ts.tv_nsec = (NSEC_PER_SEC + ts.tv_nsec);
}
return ts;
}
void pthread_nanosleep(struct timespec t) {
//Sleep calls on Linux get interrupted by signals, causing premature wake
//Pthread (un)pause is built using signals
//Therefore we need self-restarting sleep implementation
//IO timeouts are restarted by SA_RESTART, but sleeps do need explicit restart
//We also need to sleep using absolute time, because relative time is paused
//You should use this in any thread that gets (un)paused
struct timespec wake;
clock_gettime(CLOCK_MONOTONIC, &wake);
t = timespec_normalise(t);
wake.tv_sec += t.tv_sec;
wake.tv_nsec += t.tv_nsec;
wake = timespec_normalise(wake);
while(clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &wake, NULL)) if(errno!=EINTR) break;
return;
}
void pthread_nsleep(time_t s, long ns) {
struct timespec t;
t.tv_sec = s;
t.tv_nsec = ns;
pthread_nanosleep(t);
}
void pthread_sleep(time_t s) {
pthread_nsleep(s, 0);
}
void pthread_pause_yield() {
//Call this to give other threads chance to run
//Wait until last (un)pause action gets finished
sem_wait(&pthread_pause_sem);
sem_post(&pthread_pause_sem);
//usleep(0);
//nanosleep(&((const struct timespec){.tv_sec=0,.tv_nsec=1}), NULL);
//pthread_nsleep(0,1); //pthread_yield() is not enough, so we use sleep
pthread_yield();
}
void pthread_pause_handler(int signal) {
//Do nothing when there are more signals pending (to cleanup the queue)
//This is no longer needed, since we use semaphore to limit pending signals
/*
sigset_t pending;
sigpending(&pending);
if(sigismember(&pending, PTHREAD_XSIG_STOP)) return;
if(sigismember(&pending, PTHREAD_XSIG_CONT)) return;
*/
//Post semaphore to confirm that signal is handled
sem_post(&pthread_pause_sem);
//Suspend if needed
if(signal == PTHREAD_XSIG_STOP) {
sigset_t sigset;
sigfillset(&sigset);
sigdelset(&sigset, PTHREAD_XSIG_STOP);
sigdelset(&sigset, PTHREAD_XSIG_CONT);
sigsuspend(&sigset); //Wait for next signal
} else return;
}
void pthread_pause_enable() {
//Having signal queue too deep might not be necessary
//It can be limited using RLIMIT_SIGPENDING
//You can get runtime SigQ stats using following command:
//grep -i sig /proc/$(pgrep binary)/status
//This is no longer needed, since we use semaphores
//struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32};
//setrlimit(RLIMIT_SIGPENDING, &sigq);
pthread_pause_init();
//Prepare sigset
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, PTHREAD_XSIG_STOP);
sigaddset(&sigset, PTHREAD_XSIG_CONT);
//Register signal handlers
//signal(PTHREAD_XSIG_STOP, pthread_pause_handler);
//signal(PTHREAD_XSIG_CONT, pthread_pause_handler);
//We now use sigaction() instead of signal(), because it supports SA_RESTART
const struct sigaction pause_sa = {
.sa_handler = pthread_pause_handler,
.sa_mask = sigset,
.sa_flags = SA_RESTART,
.sa_restorer = NULL
};
sigaction(PTHREAD_XSIG_STOP, &pause_sa, NULL);
sigaction(PTHREAD_XSIG_CONT, &pause_sa, NULL);
//UnBlock signals
pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
}
void pthread_pause_disable() {
//This is important for when you want to do some signal unsafe stuff
//Eg.: locking mutex, calling printf() which has internal mutex, etc...
//After unlocking mutex, you can enable pause again.
pthread_pause_init();
//Make sure all signals are dispatched before we block them
sem_wait(&pthread_pause_sem);
//Block signals
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, PTHREAD_XSIG_STOP);
sigaddset(&sigset, PTHREAD_XSIG_CONT);
pthread_sigmask(SIG_BLOCK, &sigset, NULL);
sem_post(&pthread_pause_sem);
}
int pthread_pause(pthread_t thread) {
sem_wait(&pthread_pause_sem);
//If signal queue is full, we keep retrying
while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000);
pthread_pause_yield();
return 0;
}
int pthread_unpause(pthread_t thread) {
sem_wait(&pthread_pause_sem);
//If signal queue is full, we keep retrying
while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000);
pthread_pause_yield();
return 0;
}
void *thread_test() {
//Whole process dies if you kill thread immediately before it is pausable
//pthread_pause_enable();
while(1) {
//Printf() is not async signal safe (because it holds internal mutex),
//you should call it only with pause disabled!
//Will throw helgrind warnings anyway, not sure why...
//See: man 7 signal-safety
pthread_pause_disable();
printf("Running!\n");
pthread_pause_enable();
//Pausing main thread should not cause deadlock
//We pause main thread here just to test it is OK
pthread_pause(main_thread);
//pthread_nsleep(0, 1000*1000);
pthread_unpause(main_thread);
//Wait for a while
//pthread_nsleep(0, 1000*1000*100);
pthread_unpause(main_thread);
}
}
int main() {
pthread_t t;
main_thread = pthread_self();
pthread_pause_enable(); //Will get inherited by all threads from now on
//you need to call pthread_pause_enable (or disable) before creating threads,
//otherwise first (un)pause signal will kill whole process
pthread_create(&t, NULL, thread_test, NULL);
while(1) {
pthread_pause(t);
printf("PAUSED\n");
pthread_sleep(3);
printf("UNPAUSED\n");
pthread_unpause(t);
pthread_sleep(1);
/*
pthread_pause_disable();
printf("RUNNING!\n");
pthread_pause_enable();
*/
pthread_pause(t);
pthread_unpause(t);
}
pthread_join(t, NULL);
printf("DIEDED!\n");
}我也在开发名为"pthread_extra“的库,它会有类似这样的东西,甚至更多。很快就会出版。
UPDATE2:当快速调用pause/unpause (删除了sleep()调用)时,这仍然会导致死锁。glibc中的Printf()实现具有互斥量,因此如果您挂起了printf()中间的线程,然后想要从您的线程中打印出printf(),而您的线程计划稍后取消暂停该线程,这是永远不会发生的,因为printf()是锁定的。不幸的是,我删除了printf(),在线程中只运行空的while循环,但在高暂停/取消暂停的情况下仍然会出现死锁。我也不知道为什么。也许(甚至是实时的) Linux信号并不是100%安全的。有一个实时信号队列,也许它只是溢出或者别的什么。
UPDATE3:我想我已经设法修复了死锁,但必须完全重写大部分代码。现在,我为每个线程都有一个(sig_atomic_t)变量,用于保存该线程是否应该运行的状态。它的工作原理有点像条件变量。pthread_(un)pause()会为每个线程透明地记住这一点。我没有两个信号。现在我只有一个信号了。该信号的处理程序查看该变量,并且仅在sigsuspend()上阻塞该变量,当该变量指示线程不应运行时。否则,它将从信号处理程序返回。为了挂起/恢复线程,我现在将sig_atomic_t变量设置为所需的状态,并调用该信号(这对于挂起和恢复都是常见的)。使用实时信号以确保处理程序在修改状态变量后将实际运行,这一点很重要。由于线程状态数据库,代码有点复杂。我将在单独的解决方案中分享代码,一旦我设法使其足够简化。但我想在这里保留两个信号版本,因为它有点工作,我喜欢它的简单性,也许人们会给我们更多关于如何优化它的见解。
UPDATE4:我已经修复了原始代码中的死锁(不需要助手变量来保存状态),方法是对两个信号使用一个处理程序,并稍微优化一下信号队列。helgrind显示的printf()仍然有一些问题,但这不是由我的信号引起的,即使我根本不调用pause/unpause,它也会发生。总体而言,这只在LINUX上进行了测试,不确定代码的可移植性,因为似乎有一些信号处理程序的未记录行为最初导致了死锁。
请注意,暂停/取消暂停不能嵌套。如果暂停3次,取消暂停1次,线程将运行。如果你需要这样的行为,你应该创建某种类型的包装器,它将计算嵌套级别并相应地向线程发送信号。
UPDATE5:我通过以下更改提高了代码的健壮性:我通过使用信号量确保了暂停/取消暂停调用的正确序列化。这有望修复最后一个剩余的死锁。现在你可以确定当pause调用返回时,目标线程实际上已经暂停了。这也解决了信号队列溢出的问题。此外,我还添加了SA_RESTART标志,它可以防止内部信号导致IO等待中断。休眠/延迟仍然需要手动重新启动,但我提供了一个方便的包装器pthread_nanosleep(),它就是这样做的。
UPDATE6:我意识到简单地重启nanosleep()是不够的,因为在线程暂停时,超时不会运行。因此,我修改了pthread_nanosleep(),将超时间隔转换为未来的绝对时间点,并在此之前休眠。我还隐藏了信号量初始化,所以用户不需要这样做。
https://stackoverflow.com/questions/9397068
复制相似问题