最近我开始将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可以像任何时候暂停和恢复的过程一样,请告诉我一些相关的功能,我可以自己搞清楚。如果不能,我想我应该关注旧的解决方案。非常感谢。
答案 0 :(得分:4)
Modula-3垃圾收集器需要在任意时间暂停pthread,而不仅仅是在等待条件变量或互斥锁时。它通过注册一个(Unix)信号处理程序来执行它,该处理程序挂起该线程,然后使用pthread_kill向目标线程发送信号。我认为它有效(它对其他人来说是可靠的,但我现在用它来调试它的问题......)虽然它有点笨拙......
Google for ThreadPThread.m3并查看惯例" StopWorld"和" StartWorld"。处理程序本身位于ThreadPThreadC.c中。
答案 1 :(得分:2)
如果在条件变量不足的特定点停止,则不能使用pthreads执行此操作。 pthread接口不包括挂起/恢复功能。
例如,请参阅答案E.4 here:
POSIX标准没有提供任何机制,线程A可以通过该机制暂停另一个线程B的执行,而无需B的协作。实现挂起/重启机制的唯一方法是让B定期检查挂起的某个全局变量请求然后暂停一个条件变量,另一个线程可以稍后发出信号重新启动B.
该FAQ的答案继续描述了一些非标准的方法,一个在Solaris中,另一个在LinuxThreads中(现在已经过时了;不要将它与Linux上的当前线程混淆);这些都不适用于你的情况。
答案 2 :(得分:1)
在 Linux 上,您可能可以设置自定义信号处理程序(例如使用 signal()),其中将包含等待另一个信号(例如使用 sigsuspend())。然后使用 pthread_kill() 或 tgkill() 发送信号。为此使用所谓的“实时信号”很重要,因为像 SIGUSR1 和 SIGUSR2 这样的普通信号不会排队,这意味着它们可能会在高负载条件下丢失。您多次发送信号,但只收到一次,因为在信号处理程序运行之前,相同类型的新信号将被忽略。因此,如果您有并发线程执行 PAUSE/RESUME ,您可能会丢失 RESUME 事件并导致死锁。另一方面,挂起的实时信号(如 SIGRTMIN+1 和 SIGRTMIN+2)没有去重,所以队列中可以同时有几个相同的 rt 信号。
免责声明:我还没有尝试过。但理论上它应该可以工作。
另见 man 7 信号安全。有一个调用列表,您可以安全地在信号处理程序中调用。幸运的是 sigsuspend() 似乎是其中之一。
更新:我这里有工作代码:
//Filename: pause.c
//Author: Tomas 'Harvie' Mudrunka 2021
//Build: CFLAGS=-lpthread make pause; ./pause
//Test: valgrind --tool=helgrind ./pause
#include <signal.h>
#include <pthread.h>
//#include <pthread_extra.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <sys/resource.h>
#define PTHREAD_XSIG_STOP (SIGRTMIN+0)
#define PTHREAD_XSIG_CONT (SIGRTMIN+1)
#define PTHREAD_XSIGRTMIN (SIGRTMIN+2) //First unused RT signal
void pthread_pause_handler(int signal) {
//Do nothing when there are more signals pending (to cleanup the queue)
sigset_t pending;
sigpending(&pending);
if(sigismember(&pending, PTHREAD_XSIG_STOP)) return;
if(sigismember(&pending, PTHREAD_XSIG_CONT)) return;
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
struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32};
setrlimit(RLIMIT_SIGPENDING, &sigq);
//Register signal handlers
signal(PTHREAD_XSIG_STOP, pthread_pause_handler);
signal(PTHREAD_XSIG_CONT, pthread_pause_handler);
//UnBlock signals
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, PTHREAD_XSIG_STOP);
sigaddset(&sigset, PTHREAD_XSIG_CONT);
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.
//Block signals
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, PTHREAD_XSIG_STOP);
sigaddset(&sigset, PTHREAD_XSIG_CONT);
pthread_sigmask(SIG_BLOCK, &sigset, NULL);
}
int pthread_pause(pthread_t thread) {
//If signal queue is full, we keep retrying
while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000);
return 0;
}
int pthread_unpause(pthread_t thread) {
//If signal queue is full, we keep retrying
while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000);
return 0;
}
void *thread_test() {
//Whole process dies if you kill thread immediately before it is pausable
//pthread_pause_enable();
while(1) {
usleep(1000*300);
//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();
}
}
int main() {
pthread_t t;
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 signal will kill whole process
pthread_create(&t, NULL, thread_test, NULL);
while(1) {
pthread_pause(t);
printf("PAUSED\n");
sleep(3);
printf("UNPAUSED\n");
pthread_unpause(t);
sleep(1);
}
pthread_join(t, NULL);
printf("DIEDED!\n");
}
我也在开发名为“pthread_extra”的库,它将包含诸如此类的内容以及更多内容。即将发布。
UPDATE2:这在快速调用暂停/取消暂停时仍然会导致死锁(删除了 sleep() 调用)。 glibc 中的 Printf() 实现具有互斥锁,因此如果您挂起 printf() 中间的线程,然后想从计划稍后取消暂停该线程的线程中执行 printf(),它将永远不会发生,因为 printf() 是锁定。不幸的是,我已经删除了 printf() 并且只在线程中运行空的 while 循环,但是我仍然在高暂停/取消暂停率下遇到死锁。我不知道为什么。也许(甚至是实时)Linux 信号不是 100% 安全的。有实时信号队列,可能只是溢出什么的...
UPDATE3:我想我已经设法修复了死锁,但不得不完全重写大部分代码。现在我每个线程都有一个 (sig_atomic_t) 变量,用于保存该线程是否应该运行的状态。有点像条件变量。 pthread_(un)pause() 为每个线程透明地记住这一点。我没有两个信号。现在我只有一个信号。该信号的处理程序查看该变量,并且仅在该变量表示该线程不应运行时阻塞 sigsuspend()。否则它从信号处理程序返回。为了挂起/恢复线程,我现在将 sig_atomic_t 变量设置为所需状态并调用该信号(挂起和恢复都很常见)。使用实时信号以确保处理程序在修改状态变量后实际运行很重要。由于线程状态数据库,代码有点复杂。一旦我设法简化它,我将在单独的解决方案中共享代码。但我想在这里保留两个信号版本,因为它有点工作,我喜欢简单,也许人们会给我们更多关于如何优化它的见解。
UPDATE4:我已经修复了原始代码中的死锁(不需要保持状态的辅助变量)通过对两个信号使用单个处理程序并稍微优化信号队列。 helgrind 显示的 printf() 仍然存在一些问题,但这不是由我的信号引起的,即使我根本不调用暂停/取消暂停也会发生。总的来说,这仅在 LINUX 上进行了测试,不确定代码的可移植性如何,因为似乎有一些信号处理程序的未记录行为最初导致了死锁。
请注意,暂停/取消暂停不能嵌套。如果您暂停 3 次并取消暂停 1 次,则线程将运行。如果您需要这样的行为,您应该创建某种包装器来计算嵌套级别并相应地向线程发出信号。
答案 3 :(得分:0)
以下是具有暂停/恢复功能的类中的线程函数示例...
class SomeClass
{
public:
// ... construction/destruction
void Resume();
void Pause();
void Stop();
private:
static void* ThreadFunc(void* pParam);
pthread_t thread;
pthread_mutex_t mutex;
pthread_cond_t cond_var;
int command;
};
SomeClass::SomeClass()
{
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond_var, NULL);
// create thread in suspended state..
command = 0;
pthread_create(&thread, NULL, ThreadFunc, this);
}
SomeClass::~SomeClass()
{
// we should stop the thread and exit ThreadFunc before calling of blocking pthread_join function
// also it prevents the mutex staying locked..
Stop();
pthread_join(thread, NULL);
pthread_cond_destroy(&cond_var);
pthread_mutex_destroy(&mutex);
}
void* SomeClass::ThreadFunc(void* pParam)
{
SomeClass* pThis = (SomeClass*)pParam;
timespec time_ns = {0, 50*1000*1000}; // 50 milliseconds
while(1)
{
pthread_mutex_lock(&pThis->mutex);
if (pThis->command == 2) // command to stop thread..
{
// be sure to unlock mutex before exit..
pthread_mutex_unlock(&pThis->mutex);
return NULL;
}
else if (pThis->command == 0) // command to pause thread..
{
pthread_cond_wait(&pThis->cond_var, &pThis->mutex);
// dont forget to unlock the mutex..
pthread_mutex_unlock(&pThis->mutex);
continue;
}
if (pThis->command == 1) // command to run..
{
// normal runing process..
fprintf(stderr, "*");
}
pthread_mutex_unlock(&pThis->mutex);
// it's important to give main thread few time after unlock 'this'
pthread_yield();
// ... or...
//nanosleep(&time_ns, NULL);
}
pthread_exit(NULL);
}
void SomeClass::Stop()
{
pthread_mutex_lock(&mutex);
command = 2;
pthread_cond_signal(&cond_var);
pthread_mutex_unlock(&mutex);
}
void SomeClass::Pause()
{
pthread_mutex_lock(&mutex);
command = 0;
// in pause command we dont need to signal cond_var because we not in wait state now..
pthread_mutex_unlock(&mutex);
}
void SomeClass::Resume()
{
pthread_mutex_lock(&mutex);
command = 1;
pthread_cond_signal(&cond_var);
pthread_mutex_unlock(&mutex);
}