Упрощенный СТД::выполнение резьбы с использованием pthreads не


Фон

Я беру операционные системы класса в этом семестре, и мне было разрешено в C++, но не std::thread или любые другие объекты, которые присутствуют в Linux сама. Я решил построить свой собственный, и я был приятно удивлен, что это довольно легко построить базовую версию. Я не намерен дублировать std::threadКак я считаю, есть некоторые функции, которые сложно воплотить в жизнь.


Код с небольших испытаний

#ifndef CPP_THREADS_THREAD_HPP
#define CPP_THREADS_THREAD_HPP

#include <pthread.h>
#include <utility>
#include <stdexcept>
#include <string.h>
#include <tuple>

namespace shino
{
    namespace details
    {
        template<typename Lambda>
        void *call_target(void *target)
        {
            Lambda *lambda = reinterpret_cast<Lambda *>(target);
            (*lambda)();
            //don't forget to cleanup
            delete lambda;
            return nullptr;
        }

        template <typename Callable, typename ... Types>
        class arguments_wrapper
        {
            Callable callable;
            std::tuple<std::decay_t<Types>...> args;
        public:
            arguments_wrapper(Callable&& callable, Types&& ... args) :
                    callable(std::forward<Callable>(callable)),
                    args{std::forward<Types>(args)...}
            {}

            void operator()()
            {
                apply(std::make_index_sequence<sizeof...(Types)>{});
            }
        private:
            template <std::size_t ... indices>
            void apply(std::index_sequence<indices...>)
            {
                callable(std::move(std::get<indices>(args))...);
            }

        };
    }

    class thread
    {
        pthread_t thread_handle;
        bool joinable;
    public:
        using native_handle_type = pthread_t;

        template<typename Callable, typename ... ArgTypes>
        thread(Callable&& callable, ArgTypes&& ... args) :
                joinable(true)
        {
            //callable=callable causes decay, which is required to store a function pointer
            auto *target_function =
                    new details::arguments_wrapper<Callable, ArgTypes...>(std::forward<Callable>(callable),
                                                                          std::forward<ArgTypes>(args)...);
            using lambda_type = std::remove_reference_t<decltype(*target_function)>;
            auto creation_result = pthread_create(&thread_handle,
                                                  nullptr,
                                                  &details::call_target<lambda_type>,
                                                  target_function);
            if (creation_result != 0) {
                delete target_function;
                throw std::runtime_error{strerror(creation_result)};
            }
        }

        thread(const thread& other) = delete;

        thread& operator=(const thread& other) = delete;

        thread(thread&& other) :
                thread_handle(other.thread_handle),
                joinable(other.joinable)
        {
            other.thread_handle = -1;
            other.joinable = false;
        }

        thread& operator=(thread&& other)
        {
            if (joinable) {
                throw std::logic_error{"trying to drop not joined/not detached thread"};
            }

            //to be replaced by std::exchange in C++17
            thread_handle = other.thread_handle;
            joinable = other.joinable;

            other.thread_handle = -1;
            other.joinable = false;
        }

        native_handle_type native_handle()
        {
            return thread_handle;
        }

        bool is_joinable()
        {
            return joinable;
        }

        void join()
        {
            if (joinable) {
                auto join_result = pthread_join(thread_handle, NULL);
                if (join_result != 0) {
                    throw std::runtime_error{strerror(join_result)};
                }
            }
            joinable = false;
        }

        void detach()
        {
            if (!joinable) {
                throw std::logic_error{"trying to detach already joined/detached thread"};
            }
            joinable = false;
        }

        ~thread() noexcept(false)
        {
            if (joinable) {
                throw std::logic_error{"a thread was left not joined/not detached"};
            }
        }
    };
}

#endif //CPP_THREADS_THREAD_HPP

#include <iostream>
#include <thread>

void print_hello(int x, int y)
{
    std::cout << "hello x: " << x << '\n'
              << "hello y: " << y << '\n';
}

void count_until_ten()
{
    for (int i = 0; i < 9; ++i)
    {
        std::cout << i << ' ';
    }

    std::cout << '\n';
}

void waiting_func(shino::thread t)
{
    std::cout << "waiting for thread termination\n";
    t.join();
}

struct copy_move_detector
{
    copy_move_detector() = default;

    copy_move_detector(const copy_move_detector& )
    {
        std::cout << "copied\n";
    }

    copy_move_detector(copy_move_detector&&) noexcept
    {
        std::cout << "moved\n";
    }
};

void detect_copy(copy_move_detector det)
{
    std::cout << &det << '\n';
}

int main()
{
    shino::thread t1{print_hello, 1, 2};
    shino::thread t2{count_until_ten};
    waiting_func(std::move(t1));
    t2.join();

    copy_move_detector detector;

    std::cout << "testing behavior of shino::thread\n";
    std::cout << "testing move behavior\n";
    shino::thread shino_t2{detect_copy, copy_move_detector{}};
    shino_t2.join();

    std::cout << "testing copy behavior\n";
    shino::thread shino_t1{detect_copy, detector};
    shino_t1.join();

    std::cout << "testing behavior of std::thread\n";
    std::cout << "testing move behavior\n";
    std::thread std_t2{detect_copy, copy_move_detector{}};
    std_t2.join();

    std::cout << "testing copy behavior\n";
    std::thread std_t1{detect_copy, detector};
    std_t1.join();
}

Жить на Wandbox.

Как это работает

Основная суть на этой линии:

авто *target_function = новый авто(=, вызываемая = вызываемая { отзывной(параметр args...); });

Он выделяет лямда. Да, я сам удивился. new auto был добавлен в C++11.

Мне пришлось добавить arguments_wrapper для обеспечения поддержки. В основном это просто можно вызвать, и аргументы.

Стадии применения аргументов является немного более интересным. В основном он просто образует индекс последовательности, начиная с нуля sizeof...(Types). libsdc++ играли грязные и перемещен, даже если не noexceptпоэтому я решил сделать тоже так.

Потом он просто перешел к call_target, которая вызывает его при исполнении контексте меняется на вновь созданный поток. Фантик не вызовет нарушения ОУС, в качестве параметра шаблона исковеркали в имя. Вот в принципе и все.

Сюрпризы

Новая реализация превосходит с libstdc++'ы, не делая лишних движений (в ход испытаний не менее). При копировании тестирования ему даже удалось разрешить компилятору водрузить копию! в то время как с libstdc++ скопированы и перемещены в два раза. Оказалось, что моя реализация была измена, не распадаясь аргументы, хотя он по-прежнему опережает на один ход.

Проблемы

  • Можно ли избавиться от обертки?

  • Качество общего кода


Смешанная

Целевой компилятор-это GCC 5.4.

Я посмотрела реализации библиотеки libc++, и это выглядит более сложно. Я что-то пропустила?



650
5
задан 7 марта 2018 в 09:03 Источник Поделиться
Комментарии
1 ответ

Атрибуты pthread библиотеку C библиотека. Как таковой, он понимает только с АБИ. Поэтому pthread_create могут только быть переданы функции, которые имеют с АБИ, который означает, что они должны быть объявлены с extern "C".

Это означает, что вы не можете портабельно передать функцию шаблона!

7
ответ дан 7 марта 2018 в 11:03 Источник Поделиться