Реализации ожидания ресурса (в данном случае подключение к базе данных)


Я в настоящее время возникли проблемы с слишком много подключений к базе данных. Поэтому я пытаюсь реализовать базу данных тайм-аут соединения. В приведенном ниже коде изменение значения общего ресурса будут заменены подключение и отключение к базе данных. Я новичок в программирование с потоками и я любезно интересно, если я сделал какие-либо логические ошибки, или если есть какие-то более рациональные подходы к этой проблеме? Нет встроенный тайм-аут в ИЗСО, которые я использую.

#include <thread>
#include <chrono>
#include <iostream>
#include <mutex>
#include <atomic>

namespace chrono = std::chrono;

class scoped_thread{
  std::thread t;
public:
  explicit scoped_thread(std::thread t_): t(std::move(t_)){
    if ( !t.joinable()) throw std::logic_error("No thread");
  }
  ~scoped_thread(){
    t.join();
    std::cout << "Thread " << std::this_thread::get_id() << " joined.\n";
  }
  scoped_thread(scoped_thread&)= delete;
  scoped_thread& operator=(scoped_thread const &)= delete;
};

template<typename T>
class LockWrapper {
    T* t;
    std::mutex mut;

public:
    class Proxy {
        T* t;
        std::unique_lock<std::mutex> lock;
        Proxy(LockWrapper& wr) : t(wr.t), lock(wr.mut) { }
        friend LockWrapper;
    public:
        T& operator*() { return *t; }
        T* operator->() { return t; }
    };
    LockWrapper(T* t) : t(t) {}

    Proxy aquire() { return {*this}; }
};

using clock_type = chrono::system_clock;
using shared_deadline = std::atomic<clock_type::time_point>;
constexpr auto tlim=100;
template<typename T> void timeout(T& shared_resource,const shared_deadline& deadline) {
    while(clock_type::now()<deadline.load())
    {
        constexpr auto sleeping_time=1;
        std::cout << "Sleeping for another  " << 1 << " seconds\n";
        std::this_thread::sleep_until(clock_type::now()+chrono::seconds(1));
    }
    *shared_resource.aquire()=0; // will replace by database disconnect
    std::cout << "Setting resource to " << (*shared_resource.aquire()) << " after timeout occurred in thread " << std::this_thread::get_id() << ".\n";
}
int main()
{
    int int_value=1;
    LockWrapper<int> shared_resource(&int_value);
    constexpr auto timeout_length=10;
    auto get_new_deadline=[timeout_length](){return clock_type::now()+chrono::seconds(timeout_length);};
    shared_deadline deadline(get_new_deadline());
    auto th = std::make_unique<scoped_thread>(std::thread(timeout<LockWrapper<int>>, std::ref(shared_resource),std::cref(deadline)));
    constexpr int sleeping_time=20;
    for(auto t=0;t<tlim;t+=sleeping_time) // This loop simulates repeated database access
    {
        std::cout << "Slept for " << t << " seconds. Sleeping for another " << sleeping_time << " seconds\n";
        std::this_thread::sleep_for(chrono::seconds(sleeping_time));
        if((*shared_resource.aquire())==0)
        {
            *shared_resource.aquire()=1; // will replace by database connect
            std::cout << "Setting resource to " << (*shared_resource.aquire()) << "\n";
            deadline=get_new_deadline();
            th = std::make_unique<scoped_thread>(std::thread(timeout<LockWrapper<int>>, std::ref(shared_resource),std::cref(deadline)));
        }
        else
        {
            deadline=get_new_deadline();
        }
    }
}


810
5
задан 18 февраля 2018 в 06:02 Источник Поделиться
Комментарии
1 ответ

Код


  • Приобрести не приобрести.

  • В scoped_thread конструктор перемещения отсутствует &и должен иметь подпись scoped_thread(scoped_thread&&) = delete.

  • system_clock это неправильный тип часов для использования, как это не монотонная (пользователь может отрегулировать его, когда они выбирают). steady_clock вероятно, будет правильным выбором здесь.

  • Так scoped_thread магазины-нить объект в качестве членов, scoped_thread конструктор может использовать переменную шаблона Аргументы и совершенной прямой, а не проходящий в std::thread объект. Это делает пользовательский код немного короче.

  • При объявлении константы времени (tlim, sleeping_time), было бы лучше использовать std::chrono типов на этапе декларации, а не позже в коде, так что постоянное и подразделений, связанных с ним напрямую.

  • (Стиль/отзыв:) вертикали облегчает чтение кода:

    using clock_type = chrono::system_clock;
    using shared_deadline = std::atomic<clock_type::time_point>;

    constexpr auto tlim=100;

    template<typename T> void timeout(T& shared_resource,const shared_deadline& deadline)

    С немного больше интервал, это намного проще, чтобы разобрать это визуально как два с помощью объявлений, постоянное и начало функционировать.



Дизайн - замок

Есть пару мест, где ресурс будет заблокирован, а затем снова быстро разблокирован, например:

    if((*shared_resource.aquire())==0)
{
*shared_resource.aquire()=1; // will replace by database connect
std::cout << "Setting resource to " << (*shared_resource.aquire()) << "\n";

Поддержание замок будет лучше, иначе то, что печатают в std::cout не может быть, что вы ожидаете:

    auto proxy = shared_resource.aquire();

if (*proxy == 0)
{
*proxy = 1; // will replace by database connect
std::cout << "Setting resource to " << *proxy << "\n";


Дизайн - отмена / поведения ожидания


  • Если закомментировать каждую строку после создания объекта тайм-аут:

    auto th = std::make_unique<scoped_thread>(std::thread(timeout<LockWrapper<int>>, std::ref(shared_resource), std::cref(deadline)));

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


  • Нет никакого способа, чтобы отменить тайм-аут без ручной настройке срок ноль секунд в будущем (т. е. изворотливо хаки). Кроме того, исправлен сон на 1 секунду внутри timeout() значит, вы, возможно, придется ждать до второго в любом случае.

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

  • Это чревато ошибками, вручную увеличить срок каждый раз операция, которая требует использования подключения.


Изменения

Некоторые из перечисленных выше проблем может быть решена путем инкапсуляции тайм-аут функциональность в классе, и с помощью std::condition_variable чтобы сделать время ожидания. Это также позволяет обратного вызова будет отменен (или называют сразу) при желании.

class ThreadedCallback
{
public:

using clock_t = chrono::steady_clock;
using duration_t = clock_t::duration;
using action_t = std::function<void()>;

ThreadedCallback(duration_t timeout, action_t const& action)
{
start(timeout, action);
}

~ThreadedCallback()
{
cancel();
}

ThreadedCallback(ThreadedCallback&&) = delete;
ThreadedCallback(ThreadedCallback const&) = delete;

void restart(duration_t timeout, action_t const& action)
{
cancel();

start(timeout, action);
}

void cancel()
{
{
std::lock_guard<std::mutex> lock(m_mutex);

if (!m_cancelled)
{
m_cancelled = true;
m_cv.notify_one();
}
}

assert(m_thread.joinable());
m_thread.join();
}

private:

void start(duration_t timeout, action_t const& action)
{
assert(!m_thread.joinable());
m_thread = std::thread([=] () { run(timeout, action); });
}

void run(duration_t timeout, action_t const& action)
{
std::unique_lock<std::mutex> lock(m_mutex);

m_cancelled = false;
m_cv.wait_for(lock, timeout, [&] () { return m_cancelled; });

// note: this check can be removed (and cancel() renamed to call_now())
// or a boolean flag added to cancel() to indicate whether the action should still be called anyway
if (!m_cancelled)
action();
}

std::thread m_thread;
std::mutex m_mutex;
std::condition_variable m_cv;
bool m_cancelled;
};

int main()
{
int int_value = 1;
LockWrapper<int> shared_resource(&int_value);

auto disconnect = [&] ()
{
auto proxy = shared_resource.acquire();
*proxy = 0; // will replace by database disconnect
std::cout << "Setting resource to " << *proxy << " after timeout occurred in thread " << std::this_thread::get_id() << ".\n";
};

auto timeout = std::chrono::seconds(5);
ThreadedCallback callback(timeout, disconnect);

constexpr auto tlim = std::chrono::seconds(50);
constexpr auto sleeping_time = std::chrono::seconds(2);
for (auto t = std::chrono::seconds(0); t < tlim; t += sleeping_time) // This loop simulates repeated database access
{
std::cout << "Slept for " << t.count() << " seconds. Sleeping for another " << sleeping_time.count() << " seconds\n";
std::this_thread::sleep_for(sleeping_time);

auto proxy = shared_resource.acquire();

if (*proxy == 0)
{
*proxy = 1; // will replace by database connect
std::cout << "Setting resource to " << *proxy << "\n";

callback.restart(timeout, disconnect);
}
else
{
callback.restart(timeout, disconnect);
}
}

std::cout << "done!" << std::endl;
}

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

Например, поскольку мы хотим, чтобы время после последнего доступа Proxy класса деструктор может начать тайм-аут (например, вызов функции в LockWrapper для создания ThreadedCallback в unique_pointer), и Proxy конструктор можно вызвать функцию, чтобы понять, что указатель (отмена операции отключения).

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

3
ответ дан 19 февраля 2018 в 07:02 Источник Поделиться