如果I/O读()在阻塞阶段,如何退出Ctrl + C++程序?
我正在ROS环境中工作,并试图在并行线程上阅读CANBUS。我在主线程中初始化了CAN总线,因为我想确保CAN电缆已连接。通过初始化,我指的是setsockopt、ioctl、bind来配置套接字如果I/O读()在阻塞阶段,如何退出Ctrl + C++程序?,c++,multithreading,io,ros,can-bus,C++,Multithreading,Io,Ros,Can Bus,我正在ROS环境中工作,并试图在并行线程上阅读CANBUS。我在主线程中初始化了CAN总线,因为我想确保CAN电缆已连接。通过初始化,我指的是setsockopt、ioctl、bind来配置套接字 void readCanbus(int soktId) { while(true) int nbytes = read(soktId, ...); } int main() { int soktId; someSocketSetupFn(soktId);
void readCanbus(int soktId) {
while(true)
int nbytes = read(soktId, ...);
}
int main() {
int soktId;
someSocketSetupFn(soktId);
std::thread t(readCanbus, soktId);
t.join();
}
问题:如果没有传入CAN消息,则读取被阻止。Ctrl+C不会终止C++11程序
如何使读取终止,从而终止整个程序
这篇文章为POSIX提出了一个解决方案。我正在研究ubuntu16.04。下面是一个小示例,说明如何使用自管道技巧在收到CTRL-C时使I/O线程正常退出。请注意,为简单起见,示例中的I/O事件循环在主线程中完成,而不是在单独的线程中完成,但无论事件循环在哪个线程中,该技术都可以工作-信号处理程序回调将一个字节写入管道的一端,这会导致管道另一端的螺纹select ing从select返回,管道的fd处于ready to read状态。一旦通过FD_ISSET检测到,I/O事件循环就知道该退出了
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
int _signalHandlerFD;
static void MySignalHandler(int sig)
{
if (sig == SIGINT)
{
printf("Control-C/SIGINT detected! Signaling main thread to shut down\n");
char junk = 'x';
if (write(_signalHandlerFD, &junk, sizeof(junk)) != sizeof(junk)) perror("send");
}
}
/** Sets the given socket to blocking-mode (the default) or non-blocking mode
* In order to make sure a given socket never blocks inside recv() or send(),
* call SetSocketBlockingEnabled(fd, false)
*/
bool SetSocketBlockingEnabled(int fd, bool blocking)
{
if (fd < 0) return false;
#ifdef _WIN32
unsigned long mode = blocking ? 0 : 1;
return (ioctlsocket(fd, FIONBIO, &mode) == 0) ? true : false;
#else
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return false;
flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
return (fcntl(fd, F_SETFL, flags) == 0) ? true : false;
#endif
}
int main(int, char **)
{
// Create a pipe that our signal-handler can use
// to signal our I/O thread
int pipefds[2];
if (pipe(pipefds) != 0)
{
perror("pipe");
exit(10);
}
_signalHandlerFD = pipefds[1]; // the "write" end of the pipe
// Install our signal handler
if (signal(SIGINT, MySignalHandler) != 0)
{
perror("signal");
exit(10);
}
// Now we do our I/O event loop (a real program might
// do this in a separate thread, but it can work anywhere
// so for simplicity I'm doing it here)
const int timeToQuitFD = pipefds[0];
while(1)
{
fd_set readFDs;
FD_ZERO(&readFDs);
FD_SET(timeToQuitFD, &readFDs);
int maxFD = timeToQuitFD;
// If you have other sockets you want to read from,
// call FD_SET(theSocket, &readFDS) on them here, and
// update maxFD be to the maximum socket-fd value across
// of all of the sockets you want select() to watch
// select() will not return until at least one socket
// specified by readFDs is ready-to-be-read-from.
if (select(maxFD+1, &readFDs, NULL, NULL, NULL) >= 0)
{
if (FD_ISSET(timeToQuitFD, &readFDs))
{
printf("Signal handler told the main thread it's time to quit!\n");
break;
}
// also call FD_ISSET() on any other sockets here, and
// read() from them iff it returns true
}
else if (errno != EINTR)
{
perror("select()");
break;
}
}
printf("main thread exiting, bye!\n");
return 0;
}
如果您想在其他线程上模拟中断行为,那么这些线程必须允许中断,并且您的信号处理线程必须将信号传递给它们。考虑下面的片段:
static volatile std::atomic<bool> quit;
static volatile std::deque<std::thread> all;
static volatile pthread_t main_thread;
void sigint_handler (int) {
if (pthread_self() == main_thread) {
write(2, "\rQuitting.\n", 11);
quit = true;
for (auto &t : all) pthread_kill(t.native_handle(), SIGINT);
} else if (!quit) pthread_kill(main_thread, SIGINT);
}
我们定义了两种安装信号处理程序的方法,一种使用signal,另一种使用sigaction,但使用提供类似于signal语义的标志
如果将套接字设置为非阻塞模式,并在select或poll内部阻塞,或者类似的方式,则可以使用自管道技巧在信号处理程序向其管道发送字节时唤醒select/poll,然后线程可以退出。如果套接字设置为非阻塞,您还可以使用ioctl-ioctlsocketfd、FIONREAD和status来确定是否没有传入消息;如果状态为>0,则有数据,否则没有数据。这就是为什么我们不使用阻塞I/O…好主意@Jeremy,但请在适当的位置写下答案,因为这不是聊天室thanks@JeremyFriesner. 谢谢你的建议。如果您不介意的话,您能分享一些代码片段吗..信号处理程序不允许有副作用。@user207421:这是一个建议,我不知道有这样的要求。这是一个建议或要求,原因是:在信号处理程序执行的操作和主应用程序看到的操作之间没有任何强制执行读取一致性的内容。@user207421:没有理由禁止从异步回调更改全局状态。需要注意的是,更改状态时不要违反从中断的代码中做出的一致性假设。@jxh,谢谢您的回答。可以用信号动作代替信号吗?
void readCanbus(int s) {
siginterrupt(SIGINT, 1);
while(!quit) {
char buf[256];
int nbytes = read(s, buf, sizeof(buf));
}
write(2, "Quit.\n", 6);
}
template <decltype(signal)>
void sighandler(int sig, sighandler_t handler) {
signal(sig, handler);
}
template <decltype(sigaction)>
void sighandler(int sig, sighandler_t handler) {
struct sigaction sa = {};
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART;
sigaction(sig, &sa, NULL);
}
int main () {
int sp[2];
main_thread = pthread_self();
socketpair(AF_UNIX, SOCK_STREAM, 0, sp);
all.push_back(std::thread(readCanbus, sp[0]));
sighandler<sigaction>(SIGINT, sigint_handler);
for (auto &t : all) t.join();
}