渲染线程带了两个工作线程,名称是 hwuiTask0 和 hwuiTask1
下面介绍下它的原理和为什么说是渲染线程的工作线程。
一、创建
这两个线程都是在 CommonPool 构造函数中启动的
CommonPool::CommonPool() {
ATRACE_CALL();
CommonPool* pool = this;
std::mutex mLock;
std::vector<int> tids(THREAD_COUNT);
std::vector<std::condition_variable> tidConditionVars(THREAD_COUNT);
// Create 2 workers
for (int i = 0; i < THREAD_COUNT; i++) {
std::thread worker([pool, i, &mLock, &tids, &tidConditionVars] {
{
std::array<char, 20> name{"hwuiTask"};
snprintf(name.data(), name.size(), "hwuiTask%d", i);
auto self = pthread_self();
pthread_setname_np(self, name.data());
{
std::unique_lock lock(mLock);
tids[i] = pthread_gettid_np(self);
tidConditionVars[i].notify_one();
}
setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND);
auto startHook = renderthread::RenderThread::getOnStartHook();
if (startHook) {
startHook(name.data());
}
}
pool->workerLoop();
});
worker.detach();
}
{
std::unique_lock lock(mLock);
for (int i = 0; i < THREAD_COUNT; i++) {
while (!tids[i]) {
tidConditionVars[i].wait(lock);
}
}
}
mWorkerThreadIds = std::move(tids);
}
启动两个线程,优先级设置为 PRIORITY_FOREGROUND,worker.detach() 和创建线程断开联系。
工作函数是 CommonPool::workerLoop,这个函数是
void CommonPool::workerLoop() {
std::unique_lock lock(mLock);
while (true) {
if (!mWorkQueue.hasWork()) {
mWaitingThreads++;
mCondition.wait(lock);
mWaitingThreads--;
}
// Need to double-check that work is still available now that we have the lock
// It may have already been grabbed by a different thread
while (mWorkQueue.hasWork()) {
auto work = mWorkQueue.pop();
lock.unlock();
work();
lock.lock();
}
}
}
工作函数主要工作:
在锁 mLock 的保护下从 mWorkQueue 中去一个 work 去执行。
这里需要理解两个工作线程的工作函数是同一个,所以都是从 mWorkQueue 中获取 work 去执行。
二、任务来源 mWorkQueue
从上面知道工作线程的工作来源于 mWorkQueue ,它是个什么呢?
它是 CommonPool 类的成员
ArrayQueue<Task, QUEUE_SIZE> mWorkQueue;
ArrayQueue 类是 CommonPool.h 文件中定义的类。
template <class T, int SIZE>
class ArrayQueue {
PREVENT_COPY_AND_ASSIGN(ArrayQueue);
static_assert(SIZE > 0, "Size must be positive");
public:
ArrayQueue() = default;
~ArrayQueue() = default;
constexpr size_t capacity() const { return SIZE; }
constexpr bool hasWork() const { return mHead != mTail; }
constexpr bool hasSpace() const { return ((mHead + 1) % SIZE) != mTail; }
constexpr int size() const {
if (mHead > mTail) {
return mHead - mTail;
} else {
return mTail - mHead + SIZE;
// 这里有问题,应该是 mHead - mTail + SIZE,不过目前不影响使用。
}
}
constexpr void push(T&& t) {
int newHead = (mHead + 1) % SIZE;
LOG_ALWAYS_FATAL_IF(newHead == mTail, "no space");
mBuffer[mHead] = std::move(t);
mHead = newHead;
}
constexpr T pop() {
LOG_ALWAYS_FATAL_IF(mTail == mHead, "empty");
int index = mTail;
mTail = (mTail + 1) % SIZE;
T ret = std::move(mBuffer[index]);
mBuffer[index] = nullptr;
return ret;
}
private:
T mBuffer[SIZE];
int mHead = 0;
int mTail = 0;
};
这样就知道了 mWorkQueue 是一个维护128个数据成员的数组的类对象,数组的数据成员是 Task,
using Task = std::function<void()>;
Task 就是一个函数。一般是 lamda 函数
三、mWorkQueue 的数据来源
3.1 直接填充的地方
mWorkQueue 是工作线程的数据来源,那谁又在给 mWorkQueue 填充数据呢?发现只有一个函数。
void CommonPool::enqueue(Task&& task) {
std::unique_lock lock(mLock);
while (!mWorkQueue.hasSpace()) {
lock.unlock();
usleep(100);
lock.lock();
}
mWorkQueue.push(std::move(task));
if (mWaitingThreads == THREAD_COUNT || (mWaitingThreads > 0 && mWorkQueue.size() > 1)) {
mCondition.notify_one();
}
}
调用 CommonPool::enqueue 的是 CommonPool::post 函数
void CommonPool::post(Task&& task) {
instance().enqueue(std::move(task));
}
post 是静态函数,enqueue 确是普通类函数
static void post(Task&& func);
void enqueue(Task&&);
所以这里使用了单例去调用 enqueue,这也是为啥工作线程放在 CommonPool 构造函数中的,这样两个工作线程使用的 mWorkQueue 就是这个单例的 mWorkQueue 。
private:
static CommonPool& instance();
CommonPool& CommonPool::instance() {
static CommonPool pool;
return pool;
}
从上知道是通过调用 CommonPool::post 函数填充的 mWorkQueue 。
3.2 谁调用了 CommonPool::post 静态函数
总共有五处
3.2.1 hwui/renderthread/CacheManager.cpp
这里有一次
class CommonPoolExecutor : public SkExecutor {
public:
virtual void add(std::function<void(void)> func) override { CommonPool::post(std::move(func)); }
};
构建了一个 CommonPoolExecutor 静态对象:
static CommonPoolExecutor sDefaultExecutor;
sDefaultExecutor 赋值给了 GrContextOptions 对象的 fExecutor,之后在skia里有多处调用 add 函数的地方,可以在opengrok上搜索 fExecutor.add。
3.2.2 hwui/pipeline/skia/SkiaPipeline.cpp
这里调用了两次
void SkiaPipeline::endCapture(SkSurface* surface) {
if (CC_LIKELY(mCaptureMode == CaptureMode::None)) { return; }
mNwayCanvas.reset();
ATRACE_CALL();
if (mCaptureSequence > 0 && mCaptureMode == CaptureMode::MultiFrameSKP) {
mMultiPic->endPage();
mCaptureSequence--;
if (mCaptureSequence == 0) {
mCaptureMode = CaptureMode::None;
// Pass mMultiPic and mOpenMultiPicStream to a background thread, which will handle
// the heavyweight serialization work and destroy them. mOpenMultiPicStream is released
// to a bare pointer because keeping it in a smart pointer makes the lambda
// non-copyable. The lambda is only called once, so this is safe.
SkFILEWStream* stream = mOpenMultiPicStream.release();
CommonPool::post([doc = std::move(mMultiPic), stream]{
ALOGD("Finalizing multi frame SKP");
doc->close();
delete stream;
ALOGD("Multi frame SKP complete.");
});
}
static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) {
CommonPool::post([data, filename] {
if (0 == access(filename.c_str(), F_OK)) {
return;
}
SkFILEWStream stream(filename.c_str());
if (stream.isValid()) {
stream.write(data->data(), data->size());
stream.flush();
ALOGD("SKP Captured Drawing Output (%zu bytes) for frame. %s", stream.bytesWritten(),
filename.c_str());
}
});
}
都是 lamda 函数
3.2.3 CommonPool.cpp
这里有两处
template <class F>
static auto async(F&& func) -> std::future<decltype(func())> {
typedef std::packaged_task<decltype(func())()> task_t;
auto task = std::make_shared<task_t>(std::forward<F>(func));
post([task]() { std::invoke(*task); });
return task->get_future();
}
template <class F>
static auto runSync(F&& func) -> decltype(func()) {
std::packaged_task<decltype(func())()> task{std::forward<F>(func)};
post([&task]() { std::invoke(task); });
return task.get_future().get();
};
这两个静态函数都使用了 std::packaged_task,不过一个是异步,一个是同步,且异步时还使用了 std::make_shared,为啥异步需要而同步不需要呢?其实是为了保证在推出函数体后,task_t 在异步时不被删除,因为它需要在之后被调用执行。
调用async的地方
void CanvasContext::enqueueFrameWork(std::function<void()>&& func) {
mFrameFences.push_back(CommonPool::async(std::move(func)));
}
void DrawFrameTask::run() {
...
if (CC_UNLIKELY(frameCallback)) {
// 这里 DrawFrameTask::run 每次渲染都会执行,但不是每次 frameCallback 都存在
context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult,
frameNr = context->getFrameNumber()]() {
auto frameCommitCallback = std::move(frameCallback(syncResult, frameNr));
if (frameCommitCallback) {
context->addFrameCommitListener(std::move(frameCommitCallback));
}
});
}
runSync 目前只有在tests里有过调用。
四、用法
调用下面的静态函数即可将一些操作放在这两个工作线程
不关心任务什么时候执行完
CommonPool::post
同步:等待任务执行完
CommonPool::runSync
异步:不需要立刻等任务执行完,但执行到某些过程点时需要等任务执行完
CommonPool::async
传入的参数是 std::function<void(void)>,使用 std::move 传入。
五、总结
工作线程是在 CommonPool 的构造函数中创建,CommonPool 构造函数是在第一次调用 CommonPool::instance 静态函数构造单例时调用,CommonPool::instance 又是在 三个静态函数:getThreadIds、waitForIdle 和 post 中调用,所以是第一次调用这三个函数中的一个时创建的这两个工作线程。
waitForIdle 目前在hwui中没有使用;
getThreadIds只在 DrawFrameTask::HintSessionWrapper 的构造函数中使用一次;
所以主要的还是从 post 中调用的。
它主要是协助渲染线程处理一些后台工作,如 skia 执行gl命令画图操作、保证 framework callback 被执行,所以说它是渲染线程的工作线程。