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