[libcamera-devel] [PATCH 1/6] libcamera: Add thread support
Niklas Söderlund
niklas.soderlund at ragnatech.se
Thu Jul 11 07:00:19 CEST 2019
Hi Laurent,
Thanks for your patch.
On 2019-07-10 22:17:03 +0300, Laurent Pinchart wrote:
> The new Thread class wraps std::thread in order to integrate it with the
> Object, Signal and EventDispatcher classes. By default new threads run
> an internal event loop, and their run() method can be overloaded to
> provide a custom thread loop.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
I like it,
Reviewed-by: Niklas Söderlund <niklas.soderlund at ragnatech.se>
> ---
> include/libcamera/camera_manager.h | 2 -
> src/libcamera/camera_manager.cpp | 15 +-
> src/libcamera/include/thread.h | 61 ++++++
> src/libcamera/meson.build | 3 +
> src/libcamera/thread.cpp | 335 +++++++++++++++++++++++++++++
> 5 files changed, 403 insertions(+), 13 deletions(-)
> create mode 100644 src/libcamera/include/thread.h
> create mode 100644 src/libcamera/thread.cpp
>
> diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h
> index 633d27d17ebf..0e8881baba40 100644
> --- a/include/libcamera/camera_manager.h
> +++ b/include/libcamera/camera_manager.h
> @@ -46,8 +46,6 @@ private:
> std::vector<std::shared_ptr<PipelineHandler>> pipes_;
> std::vector<std::shared_ptr<Camera>> cameras_;
>
> - std::unique_ptr<EventDispatcher> dispatcher_;
> -
> static const std::string version_;
> };
>
> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
> index 337496c21cfc..2cf014233b05 100644
> --- a/src/libcamera/camera_manager.cpp
> +++ b/src/libcamera/camera_manager.cpp
> @@ -14,6 +14,7 @@
> #include "event_dispatcher_poll.h"
> #include "log.h"
> #include "pipeline_handler.h"
> +#include "thread.h"
> #include "utils.h"
>
> /**
> @@ -56,7 +57,7 @@ LOG_DEFINE_CATEGORY(Camera)
> */
>
> CameraManager::CameraManager()
> - : enumerator_(nullptr), dispatcher_(nullptr)
> + : enumerator_(nullptr)
> {
> }
>
> @@ -247,12 +248,7 @@ CameraManager *CameraManager::instance()
> */
> void CameraManager::setEventDispatcher(std::unique_ptr<EventDispatcher> dispatcher)
> {
> - if (dispatcher_) {
> - LOG(Camera, Warning) << "Event dispatcher is already set";
> - return;
> - }
> -
> - dispatcher_ = std::move(dispatcher);
> + Thread::current()->setEventDispatcher(std::move(dispatcher));
> }
>
> /**
> @@ -268,10 +264,7 @@ void CameraManager::setEventDispatcher(std::unique_ptr<EventDispatcher> dispatch
> */
> EventDispatcher *CameraManager::eventDispatcher()
> {
> - if (!dispatcher_)
> - dispatcher_ = utils::make_unique<EventDispatcherPoll>();
> -
> - return dispatcher_.get();
> + return Thread::current()->eventDispatcher();
> }
>
> } /* namespace libcamera */
> diff --git a/src/libcamera/include/thread.h b/src/libcamera/include/thread.h
> new file mode 100644
> index 000000000000..e881d90e9367
> --- /dev/null
> +++ b/src/libcamera/include/thread.h
> @@ -0,0 +1,61 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * thread.h - Thread support
> + */
> +#ifndef __LIBCAMERA_THREAD_H__
> +#define __LIBCAMERA_THREAD_H__
> +
> +#include <memory>
> +#include <mutex>
> +#include <thread>
> +
> +#include <libcamera/signal.h>
> +
> +namespace libcamera {
> +
> +class EventDispatcher;
> +class ThreadData;
> +class ThreadMain;
> +
> +using Mutex = std::mutex;
> +using MutexLocker = std::unique_lock<std::mutex>;
> +
> +class Thread
> +{
> +public:
> + Thread();
> + virtual ~Thread();
> +
> + void start();
> + void exit(int code = 0);
> + void wait();
> +
> + bool isRunning();
> +
> + Signal<Thread *> finished;
> +
> + static Thread *current();
> +
> + EventDispatcher *eventDispatcher();
> + void setEventDispatcher(std::unique_ptr<EventDispatcher> dispatcher);
> +
> +protected:
> + int exec();
> + virtual void run();
> +
> +private:
> + void startThread();
> + void finishThread();
> +
> + friend class ThreadData;
> + friend class ThreadMain;
> +
> + std::thread thread_;
> + ThreadData *data_;
> +};
> +
> +} /* namespace libcamera */
> +
> +#endif /* __LIBCAMERA_THREAD_H__ */
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index 97ff86e2167f..bf71524f768c 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -23,6 +23,7 @@ libcamera_sources = files([
> 'request.cpp',
> 'signal.cpp',
> 'stream.cpp',
> + 'thread.cpp',
> 'timer.cpp',
> 'utils.cpp',
> 'v4l2_controls.cpp',
> @@ -45,6 +46,7 @@ libcamera_headers = files([
> 'include/media_device.h',
> 'include/media_object.h',
> 'include/pipeline_handler.h',
> + 'include/thread.h',
> 'include/utils.h',
> 'include/v4l2_device.h',
> 'include/v4l2_subdevice.h',
> @@ -91,6 +93,7 @@ libcamera_sources += version_cpp
> libcamera_deps = [
> cc.find_library('dl'),
> libudev,
> + dependency('threads'),
> ]
>
> libcamera = shared_library('camera',
> diff --git a/src/libcamera/thread.cpp b/src/libcamera/thread.cpp
> new file mode 100644
> index 000000000000..95636ecaab53
> --- /dev/null
> +++ b/src/libcamera/thread.cpp
> @@ -0,0 +1,335 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * thread.cpp - Thread support
> + */
> +
> +#include "thread.h"
> +
> +#include <atomic>
> +
> +#include <libcamera/event_dispatcher.h>
> +
> +#include "event_dispatcher_poll.h"
> +#include "log.h"
> +
> +/**
> + * \file thread.h
> + * \brief Thread support
> + */
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(Thread)
> +
> +class ThreadMain;
> +
> +/**
> + * \brief Thread-local internal data
> + */
> +class ThreadData
> +{
> +public:
> + ThreadData()
> + : thread_(nullptr), running_(false), dispatcher_(nullptr)
> + {
> + }
> +
> + static ThreadData *current();
> +
> +private:
> + friend class Thread;
> + friend class ThreadMain;
> +
> + Thread *thread_;
> + bool running_;
> +
> + Mutex mutex_;
> +
> + std::atomic<EventDispatcher *> dispatcher_;
> +
> + std::atomic<bool> exit_;
> + int exitCode_;
> +};
> +
> +/**
> + * \brief Thread wrapper for the main thread
> + */
> +class ThreadMain : public Thread
> +{
> +public:
> + ThreadMain()
> + {
> + data_->running_ = true;
> + }
> +
> +protected:
> + void run() override
> + {
> + LOG(Thread, Fatal) << "The main thread can't be restarted";
> + }
> +};
> +
> +static thread_local ThreadData *currentThreadData = nullptr;
> +static ThreadMain mainThread;
> +
> +/**
> + * \brief Retrieve thread-local internal data for the current thread
> + * \return The thread-local internal data for the current thread
> + */
> +ThreadData *ThreadData::current()
> +{
> + if (currentThreadData)
> + return currentThreadData;
> +
> + /*
> + * The main thread doesn't receive thread-local data when it is
> + * started, set it here.
> + */
> + ThreadData *data = mainThread.data_;
> + currentThreadData = data;
> + return data;
> +}
> +
> +/**
> + * \typedef Mutex
> + * \brief An alias for std::mutex
> + */
> +
> +/**
> + * \typedef MutexLocker
> + * \brief An alias for std::unique_lock<std::mutex>
> + */
> +
> +/**
> + * \class Thread
> + * \brief A thread of execution
> + *
> + * The Thread class is a wrapper around std::thread that handles integration
> + * with the Object, Signal and EventDispatcher classes.
> + *
> + * Thread instances by default run an event loop until the exit() method is
> + * called. A custom event dispatcher may be installed with
> + * setEventDispatcher(), otherwise a poll-based event dispatcher is used. This
> + * behaviour can be overriden by overloading the run() method.
> + */
> +
> +/**
> + * \brief Create a thread
> + */
> +Thread::Thread()
> +{
> + data_ = new ThreadData;
> + data_->thread_ = this;
> +}
> +
> +Thread::~Thread()
> +{
> + delete data_->dispatcher_.load(std::memory_order_relaxed);
> + delete data_;
> +}
> +
> +/**
> + * \brief Start the thread
> + */
> +void Thread::start()
> +{
> + MutexLocker locker(data_->mutex_);
> +
> + if (data_->running_)
> + return;
> +
> + data_->running_ = true;
> + data_->exitCode_ = -1;
> + data_->exit_.store(false, std::memory_order_relaxed);
> +
> + thread_ = std::thread(&Thread::startThread, this);
> +}
> +
> +void Thread::startThread()
> +{
> + struct ThreadCleaner {
> + ThreadCleaner(Thread *thread, void (Thread::*cleaner)())
> + : thread_(thread), cleaner_(cleaner)
> + {
> + }
> + ~ThreadCleaner()
> + {
> + (thread_->*cleaner_)();
> + }
> +
> + Thread *thread_;
> + void (Thread::*cleaner_)();
> + };
> +
> + /*
> + * Make sure the thread is cleaned up even if the run method exits
> + * abnormally (for instance via a direct call to pthread_cancel()).
> + */
> + thread_local ThreadCleaner cleaner(this, &Thread::finishThread);
> +
> + currentThreadData = data_;
> +
> + run();
> +}
> +
> +/**
> + * \brief Enter the event loop
> + *
> + * This method enter an event loop based on the event dispatcher instance for
> + * the thread, and blocks until the exit() method is called. It is meant to be
> + * called within the thread from the run() method and shall not be called
> + * outside of the thread.
> + *
> + * \return The exit code passed to the exit() method
> + */
> +int Thread::exec()
> +{
> + MutexLocker locker(data_->mutex_);
> +
> + EventDispatcher *dispatcher = eventDispatcher();
> +
> + locker.unlock();
> +
> + while (!data_->exit_.load(std::memory_order_acquire))
> + dispatcher->processEvents();
> +
> + locker.lock();
> +
> + return data_->exitCode_;
> +}
> +
> +/**
> + * \brief Main method of the thread
> + *
> + * When the thread is started with start(), it calls this method in the context
> + * of the new thread. The run() method can be overloaded to perform custom
> + * work. When this method returns the thread execution is stopped, and the \ref
> + * finished signal is emitted.
> + *
> + * The base implementation just calls exec().
> + */
> +void Thread::run()
> +{
> + exec();
> +}
> +
> +void Thread::finishThread()
> +{
> + data_->mutex_.lock();
> + data_->running_ = false;
> + data_->mutex_.unlock();
> +
> + finished.emit(this);
> +}
> +
> +/**
> + * \brief Stop the thread's event loop
> + * \param[in] code The exit code
> + *
> + * This method interrupts the event loop started by the exec() method, causing
> + * exec() to return \a code.
> + *
> + * Calling exit() on a thread that reimplements the run() method and doesn't
> + * call exec() will likely have no effect.
> + */
> +void Thread::exit(int code)
> +{
> + data_->exitCode_ = code;
> + data_->exit_.store(true, std::memory_order_release);
> +
> + EventDispatcher *dispatcher = data_->dispatcher_.load(std::memory_order_relaxed);
> + if (!dispatcher)
> + return;
> +
> + dispatcher->interrupt();
> +}
> +
> +/**
> + * \brief Wait for the thread to finish
> + *
> + * This method waits until the thread finishes, or returns immediately if the
> + * thread is not running.
> + */
> +void Thread::wait()
> +{
> + if (thread_.joinable())
> + thread_.join();
> +}
> +
> +/**
> + * \brief Check if the thread is running
> + *
> + * A Thread instance is considered as running once the underlying thread has
> + * started. This method guarantees that it returns true after the start()
> + * method returns, and false after the wait() method returns.
> + *
> + * \return True if the thread is running, false otherwise
> + */
> +bool Thread::isRunning()
> +{
> + MutexLocker locker(data_->mutex_);
> + return data_->running_;
> +}
> +
> +/**
> + * \var Thread::finished
> + * \brief Signal the end of thread execution
> + */
> +
> +/**
> + * \brief Retrieve the Thread instance for the current thread
> + * \return The Thread instance for the current thread
> + */
> +Thread *Thread::current()
> +{
> + ThreadData *data = ThreadData::current();
> + return data->thread_;
> +}
> +
> +/**
> + * \brief Set the event dispatcher
> + * \param[in] dispatcher Pointer to the event dispatcher
> + *
> + * Threads that run an event loop require an event dispatcher to integrate
> + * event notification and timers with the loop. Users that want to provide
> + * their own event dispatcher shall call this method once and only once before
> + * the thread is started with start(). If no event dispatcher is provided, a
> + * default poll-based implementation will be used.
> + *
> + * The Thread takes ownership of the event dispatcher and will delete it when
> + * the thread is destroyed.
> + */
> +void Thread::setEventDispatcher(std::unique_ptr<EventDispatcher> dispatcher)
> +{
> + if (data_->dispatcher_.load(std::memory_order_relaxed)) {
> + LOG(Thread, Warning) << "Event dispatcher is already set";
> + return;
> + }
> +
> + data_->dispatcher_.store(dispatcher.release(),
> + std::memory_order_relaxed);
> +}
> +
> +/**
> + * \brief Retrieve the event dispatcher
> + *
> + * This method retrieves the event dispatcher set with setEventDispatcher().
> + * If no dispatcher has been set, a default poll-based implementation is created
> + * and returned, and no custom event dispatcher may be installed anymore.
> + *
> + * The returned event dispatcher is valid until the thread is destroyed.
> + *
> + * \return Pointer to the event dispatcher
> + */
> +EventDispatcher *Thread::eventDispatcher()
> +{
> + if (!data_->dispatcher_.load(std::memory_order_relaxed))
> + data_->dispatcher_.store(new EventDispatcherPoll(),
> + std::memory_order_release);
> +
> + return data_->dispatcher_.load(std::memory_order_relaxed);
> +}
> +
> +}; /* namespace libcamera */
> --
> Regards,
>
> Laurent Pinchart
>
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel at lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
--
Regards,
Niklas Söderlund
More information about the libcamera-devel
mailing list