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
В целом хочется отметить что понимание основ потоков и приложений нацеленных на многопроцессорные или многоядерные системы было интересным и позволило взглянуть под новым углом на привычное программирование