Небольшая новость

Всем привет! Всякому программисту положено иметь pet-проект, который он холит, лелеет и делает в свободное время.

Когда я задумался о таком, оказалось, что мой pet-проект — это собственно сайт blog2k.ru, для которого я написал свою тему для WordPress и поддерживаю собственный WordPress плагин со всякими полезными плюшками.

Буквально сегодня выложил в открытый доступ на github исходники своих наработок:

  • тема для WordPress этого сайта: b2k-theme
  • плагин с полезным функционалом: b2k-tools
  • виджет, который при добавлении в область title sidebar, показывает список постов c текущей категорией или тегом: wp-index-widget
  • плагин для виджета отображения погоды: yowindow-widget

Работу плагина wp-index-widget можно увидеть, например, здесь.

Всё выложено под лицензией MIT, можно модифицировать, изменять, брать любую часть и даже использовать в коммерческих целях.

Коммиты и форки всячески приветствуются и не осуждаются. Пул реквесты также, возможно, будут приняты. Enjoy!

Пул потоков с запуском функций с переменным количеством параметров

Всем привет! Внезапно оказался без определённого места работы и в свободное время мучаю ИИ по поводу возможных задач и вопросов на собеседованиях по C++, и некоторые задачи делаю самостоятельно.

Недавно я попросил ИИ сделать пул потоков. Я его написал заранее, но захотелось проверить, где я мог ошибиться.

ИИ написал нерабочий код, но с интересной идеей: запускать вместо стандартного std::function<void()> функции с параметрами с помощью std::packaged_task и variadic templates. Меня этот так вдохновило, что я решил таки сделать этот код рабочим. И сделал!

Кстати, мой код не нравится ИИ — он предлагает его переделать на std::condition_variable и, возможно, он прав, а я написал ерунду, таков путь!

Мой вариант на C++:

#include <vector>
#include <atomic>
#include <thread>
#include <mutex>
#include <functional>
#include <deque>
#include <optional>
#include <future>
#include <iostream>

class ThreadPool {
public:
    /// По умолчанию количество потоков равно количеству аппаратных потоков
    ThreadPool(size_t numThreads=std::jthread::hardware_concurrency()) {
        m_threads.reserve(numThreads);
        for (size_t i = 0; i < numThreads; ++i) {
            m_threads.emplace_back([this](){ runLoop(); });
        }
    }

    // Принимаем на входе функцию и её аргументы, на выходе автоматически
    // оборачиваем результат работы функции в std::future
    template<typename... Args>
    auto runTaskAsync(auto f, Args&&...args) {
        // Выясняем возращаемый тип у функции
        using return_type = std::invoke_result_t<decltype(f), Args...>;

        // Заворачиваем функцию с переменным количеством аргументов в
        // std::packaged_task, у которого можно получить std::future<return_type>
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::bind(std::forward<decltype(f)>(f), std::forward<Args>(args)...)
            );

        // Получаем std::future
        auto res = task->get_future();

        if (m_Running) {
            std::unique_lock lock(m_guard);
            // Добавляем задачу в очередь на выполнение
            m_tasks.push_back([task](){(*task)();});

            // Уведомляем ожидающие потоки
            m_hasTask = true;
            // Может тут надо использовать notify_one (?)
            m_hasTask.notify_all();
        }

        return res;
    }

    // Вызываем функцию синхронно и возвращаем результат
    template<typename...Args>
    auto runTaskSync(auto f, Args &&...args) {
        return runTaskAsync(f, std::forward<Args>(args)...).get();
    }

    ~ThreadPool()
    {
        // Устанавливаем флаг остановки потоков
        m_Running = false;
        // Очищаем очередь задач
        {
            std::unique_lock lock(m_guard);
            m_tasks.clear();
        }
        // Обманом :-) выводим потоки из режима ожидания
        m_hasTask = true;
        m_hasTask.notify_all();

        // Для каждого потока
        for (auto &thread : m_threads) {
            if (thread.joinable()) {
                // Дожидаемся завершения работы
                thread.join();
            }
        }
    }
private:
    using Task = std::function<void()>;

    void runLoop() {
        while (m_Running) {
            // Ждём задачу
            m_hasTask.wait(false);

            std::optional<Task> task;

            // Забираем задачу из начала списка
            {
                std::unique_lock lock(m_guard);
                if (!m_tasks.empty()) {
                    task = m_tasks.front();
                    m_tasks.pop_front();
                }
            }

            // Задача есть, исполняем
            if (task) {
                (*task)();
            }
            else {
                // Отдаём процессорное время другим потокам
                std::this_thread::yield();
            }
        }
    }

    // Вектор потоков
    std::vector<std::thread> m_threads;
    // Атомарный флаг остановки потоков
    std::atomic_bool m_Running{ true };
    // Атомарный флаг наличия задачи
    std::atomic_bool m_hasTask{ false };
    // Очередь задач
    std::deque<Task> m_tasks;
    // Мьютекс для защиты очереди задач
    std::mutex m_guard;
};


int main()
{
    // Создаём пул потоков
    ThreadPool mgr;

    // Первая задача на две секунды, которая возвращает строку
    auto func = [](const std::string &s) {
        std::cout << "Long task1 begin..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << "Long task1 end..." << std::endl;
        return s;
    };

    // Вторая задача на пять секунд, которая ничего не возвращает
    auto func2 = []() {
        std::cout << "Long task2 begin..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(5));
        std::cout << "Long task2 end..." << std::endl;
    };

    // Запускаем первую задачу с параметром
    auto result1 = mgr.runTaskAsync(func, "task1 result");
    // Запускаем вторую задачу без параметров
    auto result2 = mgr.runTaskAsync(func2);

    // Получаем результат выполнения первой задачи и выводим в консоль
    std::cout << "Task1 result = " << result1.get() << std::endl;
    // Ждём вторую задачу
    std::cout << "Wait task2 result" << std::endl;
    result2.wait();
}

Исходник от ИИ для сравнения:

#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <stdexcept>

class ThreadPool {
public:
    // Конструктор, создающий указанное количество потоков
    ThreadPool(size_t threads) : stop(false) {
        for(size_t i = 0; i < threads; ++i) {
            workers.emplace_back([this] {
                for(;;) {
                    std::function<void()> task;
                    
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock, 
                            [this]{ return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    
                    task();
                }
            });
        }
    }
    
    // Добавление задачи в пул
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type> {
        using return_type = typename std::result_of<F(Args...)>::type;
        
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
            
        std::future<return_type> res = task->get_future();
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            
            // Не добавляем новые задачи после остановки пула
            if(stop)
                throw std::runtime_error("enqueue on stopped ThreadPool");
                
            tasks.emplace([task](){ (*task)(); });
        }
        condition.notify_one();
        return res;
    }
    
    // Деструктор, останавливающий все потоки
    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for(std::thread &worker: workers)
            worker.join();
    }

private:
    std::vector<std::thread> workers;       // Рабочие потоки
    std::queue<std::function<void()>> tasks;// Очередь задач
    
    std::mutex queue_mutex;                 // Мьютекс для синхронизации доступа к очереди
    std::condition_variable condition;      // Условная переменная для уведомлений
    bool stop;                              // Флаг остановки пула
};

Буду признателен, если вы укажете на возможные ошибки и недочёты в моём коде.

Летний гастротур по России

Зеленоградск

Изначально мы прилетели в Калининград и взяли такси до Зеленоградска. Вдоль трассы полно зелени и полей, ехать 20 минут, такси стоит недорого относительно Петербурга.

Заселились в отель «Кранц», немецкое название Зеленоградска именно такое. Отель не понравился. Стоит в два раза дороже, чем должен. Единственный плюс отеля — его расположение недалеко от берега, остальное плохо — туалет и ванная одна на два номера, завтрак советский из полуфабрикатов, кофе растворимый, вешалок в номере нет, подниматься по крутой лестнице на третий этаж сложно с пустыми руками, с чемоданами вообще шею можно сломать, по уборке в номере возникли вопросы.

Но город, в отличие от отеля, оставил самые приятные впечатления, несмотря на сильный ветер и температуру в 12-16 градусов. Так получилось, что поездка была больше гастротуром, чем туризмом, не успевали испытывать голод. Перепробовали всё, привезли лишнего весу килограмм шесть на двоих.

Напробовался рыбы разных видов и способов приготовления. Тут тебе и строганина из пеламиды, и тартар из лосося, и шаверма из тунца. Так-то рыбу я не ем особо, а тут прям ушёл в отрыв.

После приезда, отдохнув от поездки, вечером посетили колесо обозрения «Глаз Балтики» и обозрели весь Зеленоградск сверху.

Церкви в Зеленоградске получаются из католических костёлов: добавляют костёлу золотой купол и получается цыганский дворец с элементами готики. Выглядит не к месту и чужеродно на мой непритязательный взгляд. Зачем так сделано — лично мне непонятно.

Церковь трансформер из хостела

Зеленоградск считается городом кошек. Законы, подобные турецким или греческим отсутствуют, но кошек полно и выглядят они довольно упитанными.

Зеленоградск. Мурал с котами.

Есть музей кошек Мурариум в бывшей водонапорной башне. Сверху открывается хороший вид на весь Зеленоградск. Сам музей содержит коллекцию разнообразных фигурок кошек, поделок в виде кошек, открыток с кошками и так далее. Имеет смысл посетить хотя бы ради вида с последнего этажа.

Новостройки в Зеленоградске стараются делать в стиле а-ля фахверк. С высоты город выглядит приятно, и в нём хочется остаться жить. Цены на квартиры — питерские.

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

Первый вечер мы провели в ресторане «Телеграф», где я впервые увидел живого сомелье и строганину из пеламиды. Живой сомелье посоветовала оранжевое вино, которое делает винодел Никита из Севастополя, а строганина — это замороженное сырое мясо рыбы. Как ни странно, мне понравилось.

Ресторан

Во второй вечер пошли в ресторан «Балт», где вкушали скумбрию с настойками. Скумбрия прекрасна, настойки — водка с вареньем. Рыбу съели, настойки не оценили и пошли в «Телеграф», где они выше всяких похвал. Вкусы настоек в «Телеграфе» разнообразные: от малины и марципана до настоек на грибах и бородинском хлебе. Настойки очень понравились, рекомендую. Будете в Зеленоградске, обязательно зайдите в «Телеграф» хотя бы за настойками, а в «Балт» за скумбрией.

Также мы посетили кафе «Гнездо», куда пёрлись по пляжу километра четыре под шквалистым ветром и ничего там толком не съели: скумбрия с костями, угорь с куском жира, картошка залита маслом. Блюда можно есть только вприкуску с Панкреатином. Жирно, невкусно и с костями.
Ещё вкушали добротные (в отличие от гостиницы) завтраки в кафе «Бранч на море». Совсем рядом ресторан «Огонёк», — тоже посетили и остались довольны.

На Куршскую косу, к сожалению, не попали. Ну ничего, будет повод приехать.

Если у вас сложилось впечатление, что мы бродили от стола к столу, преодолевая холод голод и ветер, то так оно и было. Зеленоградск в целом приятный городок, мы бы вернулись туда поесть ещё раз. Следующим местом по плану был Светлогорск, куда мы отправились на каршеринге CityRent.

Светлогорск

Оказалось, что каршеринг берёт дополнительный оброк за переезд внутри Калининградской области между городками, ценник получился в два раза дороже такси, поэтому CityRent мы использовали первый и последний раз в жизни. Копеечку заработали, клиента потеряли — молодцы, типичный российский бизнес, как он есть.

Светлогорск. Водонапорная башня

Гостиница наша располагалась в посёлке Отрадное в двух километрах от Светлогорска. В Светлогорске делать нечего, в Отрадном тем более. В этой части путешествия не понравилось абсолютно ничего. Вдоль моря не погулять, все значимые места закрыты на перестройку или реставрацию. После семи вечера всё закрывается, смотреть не на что. Потерпев Светлогорск три дня, отправились в Калининград.

Калининград

Город Калининград и область оставили впечатление, будто территорию захватили, а как содержать — не знают, поэтому преследовало чувство упадка, и грязные хрущевки это чувство только усиливали. Что интересно: трамваи в Калининграде имеют более узкую колею, чем в Петербурге, и поэтому выглядят, как детская железная дорога. Посетили мастер-класс по изготовлению фигурок из марципана, с последующей их окраской, обзорную экскурсию по рекам и каналам. Затем побывали на подводной лодке, внутри тесно, но очень интересно. Понравился собор на острове Канта и район, где расположен уютный бар «У вас горизонт завален», где развлекались до ночи и меняли оливки на клубнику. Спустя два дня (как голова перестала болеть) отправились в Казань.

Казань

В Казани заселились в отель Innjoy, что находится на главной туристической улице Баумана, что гарантировало минимальный путь к достопримечательностям, но при этом народ гулял и шумел на улице до утра. Казань выглядит как мусульманская смесь Санкт-Петербурга и Москвы. Посетили мечеть «Кул Шариф» с музеем внутри. Ислам в Казани, можно сказать, дружелюбный, с человеческим лицом — такой, каким он и должен быть, а не тот, который демонстративно тащат с собой приезжие из аулов. В музее почитали жалобу на татар от православных исследователей на старославянском. Читали сквозь смех и слёзы.

Ермоген Митрополит их призывал в соборную церковь Пречистыя Богородицы и поучал их от божественного писания и наказывал как подобает крестьянам жить и они ученья не принимают и от Татарских обычаев не отстанут

И умерших в церкви хоронить не носят, кладут по старым своим Татарским кладбищам

И с женками , и с девками с некрещенными живут мимо своих жен

Мечеть

Кормили в отеле хорошо. На улице Баумана забрели в заведение «Рёбра на огне», которое нас совсем не впечатлило: концепция «есть руками без вилок» не понравилась, мясо жестковато, первую порцию свиных рёбер вообще принесли недожаренной. Их рёбра проигрывают Holy Ribs по всем параметрам.

Пермь

Саму Пермь толком не смотрели, прогулялись от вокзала Пермь-1 через набережную мимо особняков купцов Жирновых, зарулили в блинную, схватили по-быстрому еды и поехали на вокзал Пермь-2. Жили у родственников за городом, проживание — пять звезд, всё включено, даже laundry hotel service. «Шанежки» были такие большие, что назывались гордо «Шаньги», и я даже сразу их не распознал. Думаю, если бы тётя взялась делать «посикуньчики», они бы звались «посикуны» из-за размера.

Итог

Подведу итог: Казань и Зеленоградск хочется посетить ещё раз, когда-нибудь. Цены практически везде питерские, а зарплаты — нет. Как выживает местное население, мне неведомо. Только такси местами дешевле, но не намного. Между городами передвигались на самолётах, из Казани до Перми — поездом.

А по возвращению в Питер, мы двинули в Кингисепп, но это уже другая история, и вы её не услышите.

Как найти сигнатуру Java/Kotlin метода для работы через JNI

Для того, чтобы получить ссылку на Java метод, нужно знать его сигнатуру. Есть два стула способа:

1. Подобрать сигнатуру по таблице:

Сигнатура Java тип
V void
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L fully-qualified-class ; fully-qualified-class
[ type type[]
( arg-types ) ret-type method type

2. Получить сигнатуру через javap

%JAVA_HOME%/bin/javap -classpath "your.jar" -s com.myproject.YourClass

Пример:

Для метода:

public static void runTask(Runnable runnable) {
    runnable.run();
}

Сигнатура будет: runTask(Ljava/lang/Runnable;)V — принимает объект класса java.lang.Runnable и возвращает void.
Для встроенных классов (например android.os.Handler) ищите .jar в Android_SDK.

Совет:

Для С++ удобнее использовать библиотеку jnipp вместо jni.h

P.S. Информации подобного рода в интернете — полно, писал исключительно для себя, чтобы каждый раз не искать.

Различные касты в C++

Наткнулся на полезное видео про приведение типов в C++. Освежил знания в голове. Чтобы не забыть, тут запишу, вдруг пригодится. Видео встроено в конце статьи, можете промотать до него, если текст вам неинтересен, видео более подробное.
Автору видео и ведущему канала моё почтение — ролик про lvalue/rvalue я так полностью и не усвоил. Итак, основные способы приведения типов в языке С++ следующие:

static_cast

Проверка производится во время компиляции, сообщение о невозможности операции будет получено в момент сборки приложения. Осуществляет явное допустимое приведение типов данных, в основном, используется для преобразования между числовыми типами данных.
Нужно быть внимательным, если используете для приведения указателя на родительский класс (Base *) к указателю на наследника (Derived *), он не выполняет проверку на корректность приведения (только порядок наследования) в этом случае и молча создаст проблемы — не надо так! Лучше использовать для этого dynamic_cast.

Личный опыт: использую для того, чтобы компилятор не ругался на signed/unsigned типы и float/double.

dynamic_cast

Проверка производится во время исполнения. В случае неправильного приведения указателей будет возвращен 0, в случае ссылок исключение std::bad_cast. Использует RTTI, работает динамически, может применяться только к классам, которые имеют хоть одну виртуальную функцию (в которых есть таблица виртуальных методов).

Личный опыт: dynamic_cast предпочитаю использовать только для приведения указателя на родительский класс (Base *) к указателю на наследника (Derived *) с проверкой на 0. Никогда не использую его для ссылок, чтобы не париться с исключениями.

reinterpret_cast

Приведение указателей любых типов друг в друга без проверки, на ваш страх и риск. Говорит считать кусок памяти одного типа куском памяти другого типа. Был указатель на массив uint8_t, стал указатель на массив float. Что интересно: не генерирует код, не создаёт новых переменных. Он не изменяет битовое представление данных, а просто интерпретирует их по-другому.

Личный опыт: reinterpret_cast обычно использую, чтобы передавать различные типы через сырой указатель на область памяти (void*), но однажды я посмотрел, как побитово устроен float, и под впечатлением от увиденного написал целую статью.

const_cast

Убирает const и volatile модификаторы с переменной.

Личный опыт: даже не знаю, для чего он вам может понадобиться. В голову приходит перегрузка операторов:
T &operator[](index) и const T& operator[](index) const и вызов константного оператора из неконстантного с помощью const_cast, чтобы не дублировать код.

C-style cast

Применяет цепочку преобразований, сначала const_cast, потом static_cast, потом static_cast от const_cast, потом reinterpret_cast, потом reinterpret_cast от const_cast. Здорово, правда? Не рекомендую вам его использовать в C++.

Личный опыт: грешен, использую, когда лень много писать, но стараюсь его избегать и вам советую, потому что его невозможно найти поиском по файлу, только просматривая каждую строку глазами, а они не вечные.
Вообще, всю жизнь считал, что это аналог reinterpret_cast, но я ошибался.

std::static_pointer_cast

Аналог static_cast для приведения умных указателей std::shared_ptr.

Личный опыт: std::static_pointer_cast использую для приведения умных указателей на базовый класс (std::shared_ptr<Base>) к умному указателю на наследованный класс (std::shared_ptr<Derived>).

std::dynamic_pointer_cast

Аналог dynamic_cast для умных указателей std::shared_ptr.

Личный опыт: никогда не видел вживую и не использовал, узнал про него в сейчас лет.

std::const_pointer_cast

Аналог const_cast для умных указателей std::shared_ptr.

Личный опыт: узнал про последние два только во время написания этой статьи, никогда не использовал. Буду «блистать» на собесах.

Видео по теме

Блог Евгения Жирнова