多线程与信号

多线程环境的信号递送

在一个单进程中,信号会打断进程的执行,并且递送到进程中处理,而对于多线程环境,信号会递送给其中的一个线程,这个被递送的线程是不确定的。每个线程存在自己的信号屏蔽字,可以通过如下函数设置:

int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

该函数设置线程对应的信号屏蔽字,被屏蔽的信号会被挂起从而无法递送到对应的线程。这里有一点需要特别注意,线程的信号屏蔽字是可以向下继承的,主线程设置的信号屏蔽字可以被后续创建的线程继承。

信号的阻塞

进程环境中sigprocmask可以用来阻塞进程中信号的递送,而pthread_sigmask可以阻塞线程中的信号递送,阻塞信号的可以阻塞几乎所有的信号,但是有两个信号除外:SIGKILL和SIGSTOP。
另外为了保证程序的健壮性,一些必要的错误信号最好在程序编写时也不要阻塞,比如:

TSIGFPE,SIGILL,SIGSEGV,SIGBUS

这些信号的到来都表示此进程已经处于不可控的状态,比如算术运算错误或者内存引用错误,或者是硬件错误等。

sigwait同步处理

由于每个线程都可能接收到信号,当进程收到信号时,如果是多线程的情况,我们是无法确定是哪一个线程处理这个信号,因此多线程的异步信号处理就显得很复杂,而sigwait可以实现信号的同步处理。sigwait是从进程被阻塞而挂起的信号中,接收指定的信号,多线程环境一般都会设置一个线程sigwait专门处理信号,那么所有线程都必须要先屏蔽要处理的信号,可以利用继承关系方便达到此目的。

#include <signal.h>
int sigwait(const sigset_t *set, int *sig);

需要注意的是sigwait不改变当前线程的信号屏蔽字,只是从pending的信号中取出信号去处理,因而不会触发进程的signal handler。sigwait的第一个参数是一个信号集,指的含义是该函数所等待的信号集,它不会修改信号屏蔽字。
如果信号集中不存在的信号,即使它是处于pending状态,sigwait依然不会返回。

系统调用的中断

在多线程环境中,只有接收了信号的线程,阻塞的系统调用才会中断返回,而未接受的线程,系统调用不受影响,因此我们使用sigwait专门线程处理信号,会使系统调用的使用更加简单,工作线程肯定不会接收信号,因此不用担心被信号中断。

信号的两种处理方式

多线程环境下,信号处理有两种方式:

  1. 依然使用进程的信号处理程序处理,对于多线程环境下,signal和sigaction的处理函数是所有线程共享的,在任一个线程修改,都会影响整个进程。如果有信号递送到了该进程,它依然会有效触发,只是它所打断的线程是不确定的。
  2. sigwait来处理,如果信号被sigwait所接收,那么它肯定是挂起的,也就是说此信号一定是被阻塞递送的,因此signal或者sigaction处理函数不会处理它。

假想如下场景:多线程环境中,如果有一个信号,我们不想使用sigwait来处理,而使用进程默认的signal handler去处理,而其他信号都想被一个线程sigwait同步处理,那要如果实现呢?

  1. 该信号想要触发signal handler,一定要能够被递送到进程,或者说一定要存在一个线程允许递送它,也就是不能完全阻塞。
  2. 想要被sigwait接收的信号,一定是pending信号,那么一定要先阻塞对应的信号。
  3. 为了防止工作线程系统调用被打断,创建一个线程处理信号,保证signal handler还是sigwait都在该特定线程运行。
    示例代码:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <pthread.h>
#include "include/debug.h"


void alarm_func(int signo)
{
	pr_info("SIGALRM receive!\n");
}

void *thr_fn(void *arg)
{
	int err, signo;
	sigset_t	set,mask;

	signal(SIGALRM, alarm_func);

	sigfillset(&mask);
	sigdelset(&mask, SIGALRM);
	if ((err = pthread_sigmask(SIG_SETMASK, &mask, NULL)) != 0)
		err_exit("SIG_BLOCK error\n");

	sigfillset(&set);
	sigdelset(&set, SIGALRM);
	for (;;) {
		err = sigwait(&set, &signo);
		if (err != 0) {
			err_exit("sigwait failed\n");
		}

		switch (signo) {
		case SIGTERM:
			pr_info("Catch SIGTERM; exiting\n");
			break;
		case SIGQUIT:
			pr_info("Catch SIGQUIT; exiting\n");
			exit(0);
			break;
		case SIGINT:
			pr_info("Catch SIGINT; exiting\n");
			break;

		default:
			pr_info("Unexpected signal %d\n", signo);
		}
	}
	return(0);
}

pthread_t setup_signal_thread(void)
{
	int err;
	struct sigaction	sa;
	sigset_t	mask;
	pthread_t	tid;

	/*
	 * Block all signals in main thread
	 */
	sigfillset(&mask);
	if ((err = pthread_sigmask(SIG_SETMASK, &mask, NULL)) != 0)
		err_exit("SIG_BLOCK error\n");

	/*
	 * Create a child thread to handle SIGHUP and SIGTERM.
	 */
	err = pthread_create(&tid, NULL, thr_fn, 0);
	if (err != 0)
		err_exit("can't create thread\n");
	return tid;
}

int main()
{
	char buf[10];
	pthread_t tid;
	void *tret;

	tid = setup_signal_thread();

	pr_info("signal thread created\n");
	read(STDIN_FILENO, buf, 10);
	pr_info("read was interrupted!\n");
}

该示例在工作线程(主线程中)阻塞了所有的信号,这样后续创建的线程都会继承该屏蔽字。新创建的信号线程处理两种情况:

  1. SIGALRM 信号使用signal handler来处理,因此该线程的信号屏蔽字必须没有SIGALRM。
  2. 其他信号使用sigwait来处理,sigwait接收的信号必须是线程阻塞的信号。
    运行结果如下:
$ ./syscall_intr 
[5216] INFO:  signal thread created
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  SIGALRM receive!
[5216] INFO:  Unexpected signal 6
[5216] INFO:  Unexpected signal 6
[5216] INFO:  Unexpected signal 6
[5216] INFO:  Catch SIGQUIT; exiting

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Qt中,信号与槽是一种用于对象间通信的机制,可以在多线程环境下使用。通过信号与槽,一个对象可以发射信号,而其他对象可以连接到这个信号并执行相应的槽函数。 在多线程中使用信号与槽时,需要注意以下几点: 1. 当信号的发送与对象的槽函数的执行在不同线程中时,可能会产生临界资源的竞争问题。因此,需要使用线程间同步机制来保护共享资源的访问。 2. 在Qt中,QThread继承自QObject,因此可以使用发射信号和定义槽函数的能力。QThread默认声明了几个关键信号: - started():线程开始运行时发射的信号。 - finished():线程完成运行时发射的信号。 - terminated():线程被异常终止时发射的信号。 下面是一个示例代码,演示了在Qt中如何使用信号与槽进行多线程通信: ```cpp #include <QThread> #include <QDebug> // 自定义线程类 class MyThread : public QThread { Q_OBJECT public: void run() override { qDebug() << "Thread started"; // 执行一些耗时操作 // ... // 发射信号 emit mySignal("Hello from thread"); qDebug() << "Thread finished"; } signals: void mySignal(const QString& message); }; // 自定义槽函数 class MyObject : public QObject { Q_OBJECT public slots: void mySlot(const QString& message) { qDebug() << "Received message:" << message; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); MyThread thread; MyObject object; // 连接信号与槽 QObject::connect(&thread, SIGNAL(mySignal(QString)), &object, SLOT(mySlot(QString))); // 启动线程 thread.start(); return a.exec(); } ``` 这段代码中,我们创建了一个自定义的线程类`MyThread`,其中重写了`run()`函数,在函数中执行一些耗时操作,并发射了一个自定义的信号`mySignal`。然后,我们创建了一个自定义的对象`MyObject`,其中定义了一个槽函数`mySlot`,用于接收信号并处理。在`main()`函数中,我们创建了线程对象和对象对象,并使用`QObject::connect()`函数将信号与槽连接起来。最后,启动线程并运行Qt事件循环。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值