并发程序中的I / O.

时间:2018-01-04 11:48:36

标签: c++ concurrency io

我正在进行并发计划;它有两个线程,其中一个线程侦听来自服务器的消息,另一个线程向其发送消息。 我需要从用户那里获取命令(使用cin?)并同时显示来自服务器的消息。

我该如何处理这种情况?问题在于,如果我在收到消息时从用户那里读取命令,则用户的输入会与其他内容混淆。

提前致谢

2 个答案:

答案 0 :(得分:1)

一些替代方案

  1. 让您的命令转储自上次调用命令以来发生的所有消息。这样输出就是有限的。

  2. 让cli命令持续监控所有流量,直到按下ctrl-c(或其他一些组合键),然后它会恢复到应用程序的cli提示符。

  3. 让您的cli命令将数据发送到文件并使用tail类型工具进行监控

答案 1 :(得分:1)

我拿了旧的示例代码并尝试将其转换为MCVE。 (“最小”并不一定意味着“短”,是吗?)

这是一个非常简单的“shell”概念,它支持一个线程进行输入,而多个线程可以输出。

  1. 键盘输入完成非回显。这是不便携的。因此,我提供了函数getChar()的两个实现 - 一个用于MS Windows,另一个用于非MS Windows(实际上只考虑* ix操作系统)。后者受SO: How to implement getch() function of C in Linux?“强烈启发”。

  2. 输入字符存储在std::string

  3. 输出将删除提示和当前输入文本(重复"\b \b" resp。的输出),打印输出文本(包括换行符),然后再次打印提示和当前输入缓冲区。

  4. 输出是互斥保护以授予线程安全。

    这是示例代码miniShell.cc

    // system header:
    #ifdef _WIN32
    #include <conio.h>
    #else // (not) _WIN32
    #include <termios.h>
    #include <unistd.h>
    #include <stdio.h>
    #endif // _WIN32
    
    /// reads a character from console without echo.
    #ifdef _WIN32
    inline int getChar() { return _getch(); }
    #else // (not) _WIN32
    int getChar()
    {
      struct termios oldattr;
      tcgetattr(STDIN_FILENO, &oldattr);
      struct termios newattr = oldattr;
      newattr.c_lflag &= ~(ICANON | ECHO);
      tcsetattr(STDIN_FILENO, TCSANOW, &newattr);
      const int ch = getchar();
      tcsetattr(STDIN_FILENO, TCSANOW, &oldattr);
      return ch;
    }
    #endif // _WIN32
    
    // standard C/C++ header:
    #include <cstring>
    #include <mutex>
    #include <string>
    
    /* provides a class for a simple thread-safe mini-shell.
     *
     * It is assumed that one thread may await user input (read()) while
     * another thread may (or may not) output text from time to time.
     * The mini-shell grants that the input line is always the last line.
     */
    class Console {
    
      // variable:
      private:
        // mutex for console I/O
        std::mutex _mtx;
        // current input
        std::string _input;
        // prompt output
        std::string _prompt;
    
      // methods:
      public:
        /// constructor.
        Console() { }
    
        // disabled:
        Console(const Console&) = delete;
        Console& operator = (const Console&) = delete;
    
        // reads a line from console and returns input string
        std::string read();
    
        /* writes text to console.
         *
         * text the text
         * size size of text
         */
        void write(const char *text, size_t size);
        void write(const char *text) { write(text, strlen(text)); }
        void write(const std::string &text) { write(text.c_str(), text.size()); }
    };
    
    // standard C/C++ header:
    #include <atomic>
    #include <chrono>
    #include <iomanip>
    #include <iostream>
    #include <sstream>
    #include <thread>
    
    std::string Console::read()
    {
      { // activate prompt
        std::lock_guard<std::mutex> lock(_mtx);
        _prompt = "> "; _input.clear();
        std::cout << _prompt << std::flush;
      }
    #ifdef _WIN32
      enum { Enter = '\r', BackSpc = '\b' };
    #else // (not) _WIN32
      enum { Enter = '\n', BackSpc = 127 };
    #endif // _WIN32
      // input loop
      for (;;) {
        switch (int c = getChar()) {
          case Enter: {
            std::lock_guard<std::mutex> lock(_mtx);
            std::string input = _input;
            _prompt.clear(); _input.clear();
            std::cout << std::endl;
            return input;
          } // unreachable: break;
          case BackSpc: {
            std::lock_guard<std::mutex> lock(_mtx);
            if (_input.empty()) break; // nothing to do
            _input.pop_back();
            std::cout << "\b \b" << std::flush;
          } break;
          default: {
            if (c < ' ' || c >= '\x7f') break;
            std::lock_guard<std::mutex> lock(_mtx);
            _input += c;
            std::cout << (char)c << std::flush;
          } break;
        }
      }
    }
    
    void Console::write(const char *text, size_t len)
    {
      if (!len) return; // nothing to do
      bool eol = text[len - 1] == '\n';
      std::lock_guard<std::mutex> lock(_mtx);
      // remove current input echo
      if (size_t size = _prompt.size() + _input.size()) {
        std::cout
          << std::setfill('\b') << std::setw(size) << ""
          << std::setfill(' ') << std::setw(size) << ""
          << std::setfill('\b') << std::setw(size) << "";
      }
      // print text
      std::cout << text;
      if (!eol) std::cout << std::endl;
      // print current input echo
      std::cout << _prompt << _input << std::flush;
    }
    
    // a sample application
    
    // shared data for main thread and data processing thread
    struct Shared {
      // flag: true ... exit communication thread and main loop
      std::atomic<bool> exit;
      // flag: true ... start data processing
      std::atomic<bool> start;
      // the mini console
      Console console;
    
      // constructor.
      Shared(): exit(false), start(true) { }
    };
    
    void dataProc(Shared &shared)
    {
      while (!shared.exit) {
        // "busy" wait for start (condition would be more elegant)
        while (!shared.start) {
          if (shared.exit) return;
          std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        // do data processing
        shared.console.write("Starting data processing.");
        for (int i = 0, n = 20; i < n; ++i) {
          // "busy" wait for start (condition would be more elegant)
          if (!shared.start) {
            shared.console.write("Data processing stopped.");
            while (!shared.start) {
              if (shared.exit) return;
              std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
            shared.console.write("Data processing restarted.");
          }
          // consume some time (to simulate heavy computation)
          std::this_thread::sleep_for(std::chrono::milliseconds(250));
          // do some console output about progress
          { std::ostringstream fmt;
            fmt << "Step " << i + 1 << '/' << n;
            shared.console.write(fmt.str());
          }
        }
        shared.console.write("Data processing done.");
        shared.start = false;
      }
    }
    
    void processInput(const std::string &input, Shared &shared)
    {
      if (input == "start") shared.start = true;
      else if (input == "stop") shared.start = false;
      else if (input == "exit") shared.exit = true;
      else if (input.size()) shared.console.write("Wrong command!");
    }
    
    int main()
    {
      Shared shared;
      // start a thread for some kind of data processing
      std::thread threadDataProc(&dataProc, std::ref(shared));
      // main loop
      while (!shared.exit) {
        shared.console.write("Commands: start stop exit");
        std::string input = shared.console.read();
        processInput(input, shared);
      }
      // join data processing thread
      threadDataProc.join();
      // done
      return 0;
    }
    

    我在VS2013中对Windows 10上的cygwin的bash / Xterm进行了编译和测试。 (cygwin是我手边最接近Linux的。)

    Snapshot of miniShell VS2013/cmd.exe (black) vs. g++/bash/Xterm/cygwin (blue)

    请记住,我写了这段代码,简单比完美或舒适更重要。