27 Nov 2013 00:36:37

C++ проба многопоточности

Желание понять принципы работы многопоточности процессов и реализовать сетевое десктопное приложение вылилось в написание маленькой программы примера, здесь представлены мысли по этому поводу

 

Здесь лежит ее код https://github.com/Pavel--Ruban/Osci

 

В чем заключалась основная идея? Хотелось сделать приложение которое бы слушало постоянно сокет (своего рода встроенный сервер) и в зависимости от поступаемых данных выполняло какое-либо действие, но при этом не теряло контроля и предоставляло интерфейс для управления - использования фунукций программы, графического интерфейса и т.д.

Для реализации идеи была выбрана библиотека C++ Boost asio (асинхронные операции IO) и GTK библиотека для создания оконного облика программы, а также Boost tread для возможности распарралеливания работы окна и сервера и их взаимосвязи друг с другом.

Разберем несколько кусков кода:

Потоки:

/**
 * Gtk application thread.
 */
void thread_run_1 () {
  std::cout << "create new thread..." << std::endl;
  Glib::RefPtr<Gtk::Application> app =
    Gtk::Application::create(argc, argv,
      "org.gtkmm.examples.base");

  // Allocate in heap memory window object to share it.
  osci::managerWindow *window = new osci::managerWindow();

  // Store window pointer within global scope.
  pwindow = window;
  window->set_default_size(200, 200);

  status = app->run(*window);

  // Free memory.
  delete window;
}

/**
 * Tcp server thread to async socket listening.
 */
void thread_run_2 () {
  std::cout << "create new thread..." << std::endl;
  try {
    boost::asio::io_service io_service;
    osci::tcp_server server(io_service);
    io_service.run();
  }
  catch (std::exception& e) {
    std::cerr << e.what() << std::endl;
  }
}

int main (int argc, char *argv[]) {
  // Share input arguments.
  ::argc = argc;
  ::argv = argv;

  // Split program into two separate threads.
  boost::thread thread1(thread_run_1);
  boost::thread thread2(thread_run_2);
  thread1.join();
  thread2.join();

  return status;
}


Поток по сути (в данном контексте) запуск функции, которая может, например в бесконечном цикле совершать действия и после завершения итерации ожидать сигнала от операционной системы для того чтобы выйти из спячки и обработать поступившие данные (например запрос по сети на забинденный сокет).

В функции thread_run_2 инициализируется сервер и начинает асинхронно слушать заданный сокет на предмет поступления данных. 

Поток по своей сути есть набор процессорных инструкций, который существует в контексте выделенной памяти - heap, stack, память данных. У двух потоков одного процесса эта память общая (не считая памяти стека), что означает один поток может менять состояние данных для другого потока и использовать общие объекты классов и другие сущности доступные в общей памяти. Смысл такой что здесь в глобальной области содержится указатель(адрес памяти) на экземпляр объекта окна GTK, запущенного в отдельном потоке, при поступлении запроса, операционная система запускает спящий второй поток и он в свою очередь по ссылке в глобольном пространстве вызывает методы объекта GTK окна - такие как обновить содержимое окна и поступившие данные мнгновенно отображаются на экране. Затем сервер снова начинает слушать сокет для нового запроса и засыпает, при этом, во время работы сервера операционная система может переключать потоки (если на процессоре несколько ядер, они могут работать парралельно). Это означает что во время работы сервера и обработки запроса приложение может быть доступно для выполнения другой работы.
 

#include <ctime>
#include "asyncServer.hpp"
#include "../manager/window.hpp"

// Pointer to Manager window.
extern osci::managerWindow *pwindow;

std::string osci::make_daytime_string() {
  using namespace std; // For time_t, time and ctime;
  time_t now = time(0);
  pwindow->set_title("dadada");
  std::string stime = ctime(&now);
  pwindow->m_Label1.set_label(stime);
  return stime;
}

// Tcp connection delcarations.
tcp::socket& osci::tcp_connection::socket() {
  return socket_;
}

void osci::tcp_connection::start() {
  message_ = osci::make_daytime_string();

  boost::asio::async_write(socket_, boost::asio::buffer(message_),
    boost::bind(&osci::tcp_connection::handle_write, shared_from_this(),
    boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}

В реализациях методов объекта сервера видно, что в момент соединения (void osci::tcp_connection::start()) вызывается функция osci::make_daytime_sting() которая в свою очередь использует расшаренный в глобольной области видимости внешний указатель на окно и использует его методы для управления содержимом окна и записи каких-то данных.

Если вы скомпили программу из исходников на гитхабе и хотите посмотреть на то как реагирует окно на запросы, пошлите на 1090 порт любой запрос, наприме из bash командой:

netcat 127.0.0.1 1090


В целом хочется отметить что понимание основ потоков и приложений нацеленных на многопроцессорные или многоядерные системы было интересным и позволило взглянуть под новым углом на привычное программирование

 

Comments:

add comment