在异步TCP服务器的上下文中从N-theads访问数据时的线程安全性

时间:2018-05-12 08:45:30

标签: c++ multithreading boost-asio

正如标题所说我有一个关于以下场景的问题(简单的例子):

假设我有一个下面的Generator-Class对象,它不断更新其dataChunk成员(在主线程中运行)。

class Generator
{
  void generateData();
  uint8_t dataChunk[999];
}

此外我有一个异步。 1-N客户端可以连接到的TCP连接的接受者(在第二个线程中运行)。 接受者为每个新的客户端连接启动一个新线程,其中下面的Connection类的一个对象从客户端接收请求消息,并提供一小部分dataChunk(属于Generator)作为答案。然后等待新的请求等等......

class Connection
{

  void setDataChunk(uint8_t* dataChunk);
  void handleRequest();
  uint8_t* dataChunk;
}

最后一个实际问题:所需的行为是Generator对象生成一个新的dataChunk,并等待所有1-N Connection对象与其客户端请求交叉,直到它生成一个新的dataChunk。

当Connection对象处理其请求时,如何锁定dataChunk以写入Generator对象的访问权限,但是在它们各自的线程中的所有Connection对象在其请求处理阶段期间应该同时具有读取访问权限

另一方面,Connection对象在处理各自的请求后应该等待新的dataChunk,而不会丢弃新的客户端请求。

- >我认为一个互斥锁不会在这里做到这一点。

我的第一个想法是在对象之间共享一个结构,其中包含Generator的信号量和连接的信号量向量。有了这些,每个对象都可以理解"全系统的状态并相应地工作。

你们怎么想?在这种情况下,最佳做法是什么?

提前致谢!

2 个答案:

答案 0 :(得分:1)

有几种方法可以解决它。

您可以使用std::shared_mutex

void Connection::handleRequest()
{
    while(true)
    {
        std::shared_lock<std::shared_mutex> lock(GeneratorObj.shared_mutex);
        if(GeneratorObj.DataIsAvailable()) // we need to know that data is available
        {
            // Send to client
            break;
        }
    }
}

void Generator::generateData()
{
    std::unique_lock<std::shared_mutex> lock(GeneratorObj.shared_mutex);

    // Generate data
}

或者您可以使用boost::lockfree::queue,但数据结构会有所不同。

答案 1 :(得分:0)

  

当Connection对象处理其请求时,如何锁定dataChunk以写入Generator对象的访问权限,但是在它们各自的线程中的所有Connection对象在其请求处理阶段期间应该同时具有读取访问权限

我会制作一个逻辑链操作,包括生成。

以下是一个示例:

  • 它是完全单线程的
  • 接受无限连接并处理断开的连接
  • 在等待向(多个)连接发送chunck时,它使用deadline_timer对象来发出屏障信号。这样可以方便地将generateData调用放入异步调用链中。

<强> Live On Coliru

#include <boost/asio.hpp>
#include <list>
#include <iostream>

namespace ba = boost::asio;
using ba::ip::tcp;
using boost::system::error_code;

using Clock = std::chrono::high_resolution_clock;
using Duration = Clock::duration;
using namespace std::chrono_literals;

struct Generator {
    void generateData();
    uint8_t dataChunk[999];
};

struct Server {
    Server(unsigned short port) : _port(port) {
        _barrier.expires_at(boost::posix_time::neg_infin);

        _acc.set_option(tcp::acceptor::reuse_address());
        accept_loop();
    }

    void generate_loop() {
        assert(n_sending == 0);

        garbage_collect(); // remove dead connections, don't interfere with sending

        if (_socks.empty()) {
            std::clog << "No more connections; pausing Generator\n";
        } else {
            _gen.generateData();
            _barrier.expires_at(boost::posix_time::pos_infin);

            for (auto& s : _socks) {
                ++n_sending;
                ba::async_write(s, ba::buffer(_gen.dataChunk), [this,&s](error_code ec, size_t written) {
                    assert(n_sending);
                    --n_sending; // even if failed, decreases pending operation
                    if (ec) {
                        std::cerr << "Write: " << ec.message() << "\n";
                        s.close();
                    }
                    std::clog << "Written: " << written << ", " << n_sending << " to go\n";

                    if (!n_sending) {
                        // green light to generate next chunk
                        _barrier.expires_at(boost::posix_time::neg_infin);
                    }
                });
            }

            _barrier.async_wait([this](error_code ec) {
                if (ec && ec != ba::error::operation_aborted)
                    std::cerr << "Client activity: " << ec.message() << "\n";
                else generate_loop();
            });
        }
    }

    void accept_loop() {
        _acc.async_accept(_accepting, [this](error_code ec) {
                if (ec) {
                    std::cerr << "Accept fail: " << ec.message() << "\n";
                } else {
                    std::clog << "Accepted: " << _accepting.remote_endpoint() << "\n";
                    _socks.push_back(std::move(_accepting));

                    if (_socks.size() == 1) // first connection?
                        generate_loop();    // start generator

                    accept_loop();
                }
            });
    }

    void run_for(Duration d) {
        _svc.run_for(d);
    }

    void garbage_collect() {
        _socks.remove_if([](tcp::socket& s) { return !s.is_open(); });
    }
  private:
    ba::io_service _svc;
    unsigned short _port;
    tcp::acceptor _acc { _svc, { {}, _port } };
    tcp::socket _accepting {_svc};

    std::list<tcp::socket> _socks;

    Generator _gen;
    size_t n_sending = 0;
    ba::deadline_timer _barrier {_svc};
};

int main() {
    Server s(6767);
    s.run_for(3s); // COLIRU
}

#include <fstream>
// synchronously generate random data chunks
void Generator::generateData() {
    std::ifstream ifs("/dev/urandom", std::ios::binary);
    ifs.read(reinterpret_cast<char*>(dataChunk), sizeof(dataChunk));
    std::clog << "Generated chunk: " << ifs.gcount() << "\n";
}

打印(仅适用于1个客户):

Accepted: 127.0.0.1:60870
Generated chunk: 999
Written: 999, 0 to go
Generated chunk: 999
   [... snip ~4000 lines ...]
Written: 999, 0 to go
Generated chunk: 999
Write: Broken pipe
Written: 0, 0 to go
No more connections; pausing Generator