如何中断其他 std::threads c++

How to interrupt other std::threads c++

本文关键字:std threads c++ 其他 何中断 中断      更新时间:2023-10-16

我有一个以每个客户端线程的方式构建的服务器。最近,我遇到了一个问题,我很难想出解决方案,所以我想寻求一些帮助。

我的服务器拥有一个大厅,大厅中有许多房间(全部归用户所有),房间中有玩家。每个房间都有一个管理员,当管理员选择离开时 - 房间关闭,所有用户都应该返回大厅。

现在,我已经有一个工作代码 - 但问题是,我不知道我应该如何让其他客户端也退出房间。线程中运行的代码如下所示:

while(in_lobby)
{
//Receive a message
//Do stuff
//In certain cases change the Boolean to fit to the situation
//Send a comeback
}
while(in_room)
{
//Receive a message
//Do stuff
//In certain cases change the Boolean to fit to the situation
//Send a comeback
}
while(in_game)
{
//When a game started
//Not practical right now, though
}

将布尔值从一个客户端的线程弄乱到另一个客户端没有问题(因为它们不完全是局部变量,它们是我可以通过处理管理员选择关闭房间的线程更改的几个条件)。

当条件确实更改并且 while 循环应该在下一次迭代之前退出时,就会出现问题。为什么会这样?因为在迭代开始时有一个recv()调用,它等待客户端的消息。

现在,条件没问题,一切正常,但是循环不会继续(因此它不会到达下一次迭代 - 看到条件为 false),直到服务器收到来自客户端的某条消息(它不应该,因为关闭房间不依赖于普通用户 - 用户只接收并发出警报, 这也是通过管理员的线程发送的,关于房间被关闭)。

因此,我的问题是:

我怎样才能执行我想要的?我怎样才能让这些用户脱离循环,返回大厅(与管理员一起这样做没有问题,因为他的线程是执行所有操作的线程并且他成功返回大厅)而不更改整个体系结构从每个客户端的线程方式?

"因为在迭代开始时有一个 recv() 调用,它等待客户端的消息。

可能您不希望在这一点上阻止recv()呼叫排在第 1 位。
有来自 Windows 套接字 API 的select()函数,可用于观察许多套接字文件描述符的状态更改。

基本上,您将有一个线程运行循环,并轮询select()调用的返回值。为了不占用 CPU 的紧密循环,应该指定一个合理的超时值,但可以在零超时的情况下,并且也只是轮询可用状态。

如果返回值指示对其中一个观察到的套接字 (select() > 0) 有一些操作可用,则可以检查观察到的套接字文件描述符是否是使用

FD_ISSET(s, *set)

宏观。

因为在迭代开始时有一个 recv() 调用,它等待客户端的消息。

如果您正在使用什么,我不熟悉细节,但是可以在类似情况下使用的两个技巧是:

  • 让您的服务器将字节放入recv()从中读取的流中。
  • 关闭流。

意图是其中任何一个都会迫使recv()返回。

如果您不能使用 winsock 执行此操作,那么另一种解决方案是添加另一层。您编写了一个包装类,用于管理如何从套接字读取的复杂性,同时仍然能够接收来自其他源的通知。然后,客户端仅使用包装类来接收通信。

从长远来看,这个解决方案可能比直接使用recv()更好,因为它分离了关注点;客户端代码只关心如何处理客户端,而不关心如何进行健壮通信的细节,通信代码只需要处理如何接收和中继通信。

除了 πάντα ῥεῖ 的注释之外,您还可以尝试实现提供者-消费者模式。

使用队列存储客户端消息,并使用该循环从中读取,如果没有要读取的消息,请继续。这样,循环就不会等待消息到达,即"提供者"的作业(循环是使用者,因为正在使用队列中的消息)。

因此,将调用recv的代码移出循环,并将其用于馈送队列。

伪代码:

Queue queue; // This has to be thread save.
class Provider: Thread
{
void run(queue)
{
while(1)
{
message = recv();       // This is where waiting occurs.
queue.push(message);
}
}   
}

// This looks like it fits inside another thread. ;)
while (some_condition)
{
message = queue.pop(); // This returns immediately.
if (message)
{
//... so some things.  
}
}

重构您的代码,以便只有一个地方可以阻止recv.然后,您可以根据需要移动客户端,而不必中断recv中阻塞的线程。如果客户端发送消息,您仍然希望收到消息,对吗?

因此,当房间关闭时,关闭房间的线程可以将客户端移出房间,而无需打扰等待来自这些客户端的消息的线程。

Stroika 线程库 - https://github.com/SophistSolutions/Stroika/blob/v2.1-Release/Library/Sources/Stroika/Foundation/Execution/Thread.h - 支持通过取消点中断。这解决了大约 1/2 的问题。

但是,对于网络应用程序,解决其余问题的是网络库在套接字(https://github.com/SophistSolutions/Stroika/blob/v2.1-Release/Library/Sources/Stroika/Foundation/IO/Network/Socket.h)等网络对象上提供包装器,这些包装器会自动将阻塞操作转换为取消点。这允许您简单地编写网络应用程序,只需告诉线程取消/中止,任何正在进行的网络调用都会自动取消(由库)。