[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