前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Chromium】Base库的最佳实践 - 进程和线程

【Chromium】Base库的最佳实践 - 进程和线程

原创
作者头像
lealc
修改2024-06-10 16:16:18
1580
修改2024-06-10 16:16:18
举报
文章被收录于专栏:Chromium学习Chromium学习

前言

Chromium是一个开源的浏览器项目,它提供了现代Web浏览器的许多功能。Chromium的base库是该项目的核心组件之一,为整个浏览器提供了基础的功能和工具。

Base库包含各种客户端编程所需要的基础模块,例如:进程、线程、内存、时间、文件、字符串、调试等等。然而,实际应用中如何有效利用这些模块是个挑战。例如,Base库的线程模型较复杂,类之间耦合度高,导致简单应用难以直接采用。因此,我们需要从中精选模块并灵活组合。下面,我将分享一些个人实践经验和技巧,本次分享主要以进程和线程为切入口,集中于Windows平台的实践,结合QT框架以及Windows系统本身的特性来进行说明。

注:基于开源代码Base库

进程

Base库中对进程的操作很全面,主要划分为kill、launch、info、metrics四种,分别是终止进程、启动进程、遍历进程、进程性能指标

终止进程

Base库终止进程可以通过进程可执行文件的名称来进行过滤,终止指定名称对应的所有进程,有三种类型的终止进程方式

  • KillProcesses:直接终止进程,使用Terminate来退出进程并获取退出码exit_code
  • CleanupProcesses:清理进程,等待指定时间,如果期间进程退出则不进行任何操作,否则超时则直接执行KillProcesses
  • EnsureProcessTerminated:确保进程退出,会周期性(2秒)尝试KillProcesses,指导进程退出

可以发现,实际最终都会走到KillProcesses,来看一下源码实现

代码语言:C++
复制
void Process::Exited(int exit_code) const {
  base::debug::GlobalActivityTracker::RecordProcessExitIfEnabled(Pid(),
                                                                 exit_code);
}

bool Process::Terminate(int exit_code, bool wait) const {
  constexpr DWORD kWaitMs = 60 * 1000;
  
  DCHECK(IsValid());
  bool result = (::TerminateProcess(Handle(), exit_code) != FALSE);
  if (result) {
    if (wait && ::WaitForSingleObject(Handle(), kWaitMs) != WAIT_OBJECT_0)
      DPLOG(ERROR) << "Error waiting for process exit";
    Exited(exit_code);
  } else {
    if (GetLastError() != ERROR_ACCESS_DENIED)
      DPLOG(ERROR) << "Unable to terminate process";
    if (::WaitForSingleObject(Handle(), kWaitMs) == WAIT_OBJECT_0) {
      DWORD actual_exit;
      Exited(::GetExitCodeProcess(Handle(), &actual_exit) ? actual_exit
                                                          : exit_code);
      result = true;
    }
  }
  return result;
}

bool KillProcesses(const FilePath::StringType& executable_name,
                   int exit_code,
                   const ProcessFilter* filter) {
  bool result = true;
  NamedProcessIterator iter(executable_name, filter);
  while (const ProcessEntry* entry = iter.NextProcessEntry()) {
    Process process = Process::Open(entry->pid());
    if (!process.IsValid()) {
      result = false;
      continue;
    }
    result &= process.Terminate(exit_code, true);
  }
  return result;
}

KillProcesses首先会使用TerminateProcess来关闭进程,然后60秒后会使用debug中的GlobalActivityTracker记录进程退出的详情。

启动进程

Base库启动进程有多种方式,主要分为两大类:LaunchProcess和GetAppOutput*

base::LaunchProcess是一个用于启动外部进程的函数。它允许你指定命令行参数、工作目录、环境变量等,并且可以等待进程完成或异步地处理进程输出。

base::GetAppOutput是一个用于执行外部命令并捕获其标准输出的函数。它通常用于简单地执行命令并获取其输出结果。

异同点

  • 目的:LaunchProcess是为了启动和管理外部进程,而GetAppOutput是为了执行命令并获取其输出。
  • 使用复杂性:LaunchProcess提供了更多的控制和灵活性,需要更多的配置;GetAppOutput则更为简单,适合快速执行命令并获取输出。
  • 进程管理:LaunchProcess返回一个进程对象,允许你对进程进行更复杂的操作;GetAppOutput内部处理了进程的创建和执行,用户无需关心进程的管理。
  • 异步处理:LaunchProcess可以与base::Process::WaitForExit等方法结合使用来实现异步处理;GetAppOutput则是同步的,它会阻塞直到命令执行完成。

根据你的需求,可以选择使用base::LaunchProcess或base::GetAppOutput。如果你需要启动进程并进行后续操作,或者需要更多的控制,应该使用LaunchProcess。如果你只是简单地想要执行一个命令并获取它的输出,GetAppOutput会更方便。

LaunchProcess

LaunchProcess的基本实现是利用了CreateProcess系列函数来实现进程的启动,其中LaunchOptions则是主要和CreateProcess的各个参数一一对应,注意默认启动进程是不等待进程直接退出,不会卡线程,如果需要同步等待进程退出,则需要将LaunchOptions的wait置为true。

进程启动在windows上面需要额外注意UAC也就是是否管理员权限启动,有以下四种场景

非管理员权限应用 启动 非管理员权限进程 + 管理员权限应用 启动 管理员权限进程

这两种情况,应用利用CreateProcess来拉起进程,本身子进程就会继承当前进程的权限,不需要有额外的操作,正常调用LaunchProcess即可,或者自行封装CreateProcess

代码语言:C++
复制
bool Util::QtCreateProcess(const QString& program, const QString& arguments) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // 将 QString 转换为 Windows 风格的命令行参数
    QString commandLine = program + " " + arguments;

    // 创建进程
    if (!::CreateProcess(NULL, (LPWSTR)commandLine.toStdWString().c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        return false;
    }
      
    // 关闭进程和主线程句柄
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return true;
}
管理员权限应用 启动 非管理员权限进程

针对这种场景,就是常说的降权运行进程的操作,这种有两种实现方式,一种是利用CreateProcessAsUser传入用户态Token来实现用户权限启动进程,一种是explorer来代理进程启动,两种方法各有优劣

  • CreateProcessAsUser:通常,调用 CreateProcessAsUser函数的进程必须具有 SE_INCREASE_QUOTA_NAME 特权,并且如果令牌不可分配,则可能需要 SE_ASSIGNPRIMARYTOKEN_NAME 特权。
  • explorer代理:使用explorer代理启动进程,由于explorer是用户权限下运行,所以启动的进程也自然成为了用户权限下进程,但是缺点是无法携带进程参数,否则会启动失败。

CreateProcessAsUser简略实现如下,可自行适配调整使用

代码语言:C++
复制
inline constexpr const wchar_t* SLowIntegritySid = L"S-1-16-4096";
inline constexpr const wchar_t* SMediumIntegritySid = L"S-1-16-8192";
inline constexpr const wchar_t* SHighIntegritySid = L"S-1-16-12288";
inline constexpr const wchar_t* SSystemIntegritySid = L"S-1-16-16384";

operator HANDLE() {
   /* Get the current process' security token as a starting point, then modify
      a duplicate so that it runs with a fixed integrity level. */
   if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY, &m_curToken)) {
       if (::DuplicateTokenEx(m_curToken, 0, NULL, SecurityImpersonation, TokenPrimary, &m_dupToken)) {
           if (AdjustTokenIntegrityLevel(m_dupToken, m_lpSid)) {
               return m_dupToken;
           }
       }
   }
   return nullptr;
}

static BOOL AdjustTokenIntegrityLevel(HANDLE token, const wchar_t* sid) {
   /* Convert the string SID to a SID *, then adjust the token's
      privileges. */
   BOOL ret{};
   PSID psd{};
   if (::ConvertStringSidToSid(sid, &psd)) {
       TOKEN_MANDATORY_LABEL tml;

       ZeroMemory(&tml, sizeof(tml));
       tml.Label.Attributes = SE_GROUP_INTEGRITY;
       tml.Label.Sid = psd;

       ret = ::SetTokenInformation(token, TokenIntegrityLevel, &tml, sizeof(tml));
       if (!ret) {
           L_ERROR_NOCODE(L"SetTokenInformation return false, err:%d", ::GetLastError());
       }
       ::LocalFree(psd);
   }
   return ret;
}

CIntegrityLevelToken token(SMediumIntegritySid);
if (!::CreateProcessAsUser(token, NULL, (LPWSTR)commandLine.toStdWString().c_str(), NULL, NULL, FALSE, CREATE_PRESERVE_CODE_AUTHZ_LEVEL, NULL, NULL, &si, &pi)) {
    return false;
}

explorer代理实现就比较简单了,使用CreateProcess即可

代码语言:C++
复制
bool Util::CreateProcessAsUserWithExplore(const QString& program) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(STARTUPINFO);
    ZeroMemory(&pi, sizeof(pi));

    // 将 QString 转换为 Windows 风格的命令行参数
    QString commandLine = "explorer.exe " + program;

    if (!::CreateProcess(NULL, (LPWSTR)commandLine.toStdWString().c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        return false;
    }

    // 关闭进程和主线程句柄
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return true;
}
非管理员权限应用 启动 管理员权限进程

Base库实现这个操作有专门的函数:LaunchElevatedProcess,具体是通过调用ShellExecuteEx,传入“runas”参数来实现提权运行进程。

以下是SHELLEXECUTEINFO结构体中各个字段的含义:

  • cbSize:结构体的大小,以字节为单位。
  • fMask:指定要执行的操作的标志。
  • hwnd:父窗口句柄,如果不需要,可以设置为NULL。
  • lpVerb:指定要执行的操作,如"open"、"runas"(以管理员身份运行)、"print"等。
  • lpFile:要执行的文件或程序的路径。
  • lpParameters:传递给程序的参数。
  • lpDirectory:程序运行的默认目录。
  • nShow:指定窗口的显示状态,如SW_HIDE(隐藏)或SW_SHOW(显示)。
  • hInstApp:如果设置了SEE_MASK_NOCLOSEPROCESS标志,这个字段会被设置为应用程序实例的句柄。
  • lpIDList:用于指定文件或项目的PIDL(项标识符列表),通常用于拖放操作。
  • lpClass:用于指定注册表中的类名。
  • hkeyClass:用于指定注册表中的类键。
  • dwHotKey:指定与应用程序关联的热键。
  • hIcon/hMonitor:用于指定图标句柄或监视器句柄。
  • hProcess:如果设置了SEE_MASK_NOCLOSEPROCESS标志,这个字段会被设置为启动的进程的句柄。

GetAppOutput

GetAppOutput中一定会有WaitForSingleObject来等待进程退出,所以会同步卡住线程,一般执行一些简单的命令行或者进程才会使用到,否则建议使用LaunchProcess来进行进程启动会有更精确的控制。有如下的变种函数方便使用

  • GetAppOutput: 执行指定的命令行(CommandLine对象cl),等待命令执行完毕,并将标准输出(stdout)存储在output字符串中。标准错误(stderr)被重定向到/dev/null,即忽略错误输出。如果命令成功执行并正常退出(退出代码表示成功),则返回true。
代码语言:C++
复制
BASE_EXPORT bool GetAppOutput(const CommandLine& cl, std::string* output);
  • GetAppOutputAndError: 与GetAppOutput类似,但这个函数同时捕获标准输出和标准错误。这意味着命令的错误输出也会被存储在output字符串中。
代码语言:C++
复制
BASE_EXPORT bool GetAppOutputAndError(const CommandLine& cl, std::string* output);
  • GetAppOutputWithExitCode: 这个函数不仅捕获标准输出,还返回命令的退出代码。如果命令执行成功并且正常退出,返回true,并且命令的退出代码可以通过exit_code指针获得。
代码语言:C++
复制
BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl, std::string* output, int* exit_code);

基本使用示例如下

代码语言:C++
复制
#include <string>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/process/launch.h"

int main() {
  base::CommandLine cl(base::FilePath(FILE_PATH_LITERAL("myapp.exe")));
  cl.AppendArg("--arg1");
  cl.AppendArg("--arg2");

  std::string output;
  if (base::GetAppOutput(cl, &output)) {
    std::cout << "Command output:<< output<< std::endl;
  } else {
    std::cerr << "Failed to execute command."<< std::endl;
  }

  return 0;
}

遍历进程

Base库进行了较为完善的封装,使得遍历进程有这很好的体验,之前终止进程已经有过类似的代码,这里遍历进程主要是通过NamedProcessIterator和ProcessFilter来实现,其中ProcessFilter可以通过派生类来实现自定义的过滤逻辑,而NamedProcessIterator则是通过进程可执行文件名来实现过滤

代码语言:C++
复制
#include "base/process/process_iterator.h"
#include<iostream>

class MyProcessFilter : public base::ProcessFilter {
 public:
  bool Includes(const base::ProcessEntry& entry) const override {
    // 这里可以添加自定义的筛选逻辑
    return true;  // 假设我们接受所有进程
  }
};

int main() {
  base::NamedProcessIterator it("chrome.exe", new MyProcessFilter());

  base::ProcessEntry entry;
  while (it.NextProcessEntry(&entry)) {
    std::cout << "Found process:<< entry.cmd_line().value()<< std::endl;
  }

  return 0;
}

同样,也可以结合原生实现来封装更加灵活的遍历进程函数,以下是搜索指定进程名并返回其完整路径

代码语言:C++
复制
base::string16 Util::GetProcessPathAndName(HANDLE process_handle) {
    wchar_t path_buffer[MAX_PATH] = { 0 };
    DWORD path_length = MAX_PATH;
    BOOL ret = QueryFullProcessImageName(process_handle, 0, path_buffer, &path_length);
    if (ret && path_length > 0 && path_length < MAX_PATH) {
        return base::string16(path_buffer, path_length);
    }
    else {
        return L"";
    }
}

base::FilePath Util::GetProcessPathByName(const std::wstring& process_name) {
    base::FilePath process_path;
    base::win::ScopedHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
    if (!snapshot.IsValid()) {
        return process_path;
    }

    PROCESSENTRY32 entry = {};
    entry.dwSize = sizeof(entry);
    if (!Process32First(snapshot.Get(), &entry)) {
        return process_path;
    }

    do {
        if (entry.szExeFile == process_name) {
            base::win::ScopedHandle process(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, entry.th32ProcessID));
            if (process.IsValid()) {
                base::string16 path_wstring = GetProcessPathAndName(process.Get());
                if (!path_wstring.empty()) {
                    process_path = base::FilePath::FromUTF16Unsafe(path_wstring);
                    break;
                }
            }
        }
    } while (Process32Next(snapshot.Get(), &entry));

    return process_path;
}

这里面使用到ScopedHandle来避免句柄泄露,利用base库FilePath来存放路径。

性能指标

base库允许使用process_metrics来获取进程的运行性能,结合定时器可以实现对进程进行监控来更好的性能优化。

process_metrics监控各种性能指标,涵盖了:IO、内存、CPU等等,以下是一个简单的使用示例

代码语言:C++
复制
#include "base/process/process_metrics.h"
#include<iostream>

int main() {
  // 获取当前进程的句柄
  base::ProcessHandle process_handle = base::GetCurrentProcessHandle();

  // 创建一个ProcessMetrics对象
  base::ProcessMetrics metrics(process_handle);

  // 获取CPU使用率
  double cpu_usage = metrics.GetPlatformIndependentCPUUsage();
  std::cout << "Current process CPU usage:<< cpu_usage<< "%"<< std::endl;

  // 获取内存使用情况
  size_t working_set_size = metrics.GetWorkingSetSize();
  std::cout << "Current process Working Set Size:<< working_set_size<< " bytes"<< std::endl;

  return 0;
}

线程

Base库的线程模型虽然复杂且高度耦合,但许多常规应用实际上并不需要完全理解或使用这个模型。在实际应用中,我们主要利用线程来执行异步任务。为了实现这一点,Base库提供了PostTask方法,它极大地简化了异步任务的调度和执行。

鉴于此,我们将围绕PostTask方法构建一个胶水层,以使Base库的线程模型更易于使用。这个胶水层将封装并抽象出必要的功能,使我们能够在常规应用中更方便地利用Base库的线程模型。

注:这一节需要了解base库的一些基本类实现,可以参考Chromium学习

主要实现的思想是:

1、异步任务会划分为四种类型:UI(主线程)任务、IO任务、后台任务、序列任务。

2、UI任务会通过base::win::MessageWindow来实现主线程的窗口循环,利用PostMessage将任务调度到主线程中执行。

3、IO、后台、序列任务,默认使用base库中base::internal::ThreadPoolImpl的线程池创建对应的taskRunner执行。

4、启用debug\debugging_buildflags.h中ENABLE_LOCATION_SOURCE来实现对异步任务的追踪

5、封装Task来实现对异步任务耗时的监控,主要监控主线程任务是否会导致卡界面

接口一览

代码语言:C++
复制
enum class Thread_Type {
    TYPE_MAIN_UI,
    TYPE_FILE_IO,
    TYPE_BACKGROUND
};

// 普通任务丢到线程池,主线程任务丢到主线程创建的窗口循环去处理
// 异步任务接口
void PostTask(Thread_Type type, const base::Location& from_here, base::OnceClosure task);
void PostDelayedTask(Thread_Type type, const base::Location& from_here, base::OnceClosure task, base::TimeDelta delay);
bool PostTaskAndReply(Thread_Type type, const base::Location& from_here, base::OnceClosure task, base::OnceClosure reply);

// 传递参数的回复异步任务
template <template <typename> class CallbackType,
    typename TaskReturnType,
    typename ReplyArgType,
    typename = base::EnableIfIsBaseCallback<CallbackType>>
    bool PostTaskAndReplyWithResult(Thread_Type type, const base::Location& from_here,
        CallbackType<TaskReturnType()> task,
        CallbackType<void(ReplyArgType)> reply);

// 抛顺序执行任务
void PostSequnceTask(const base::Location& from_here, base::OnceClosure task);
void PostSequnceDelayedTask(const base::Location& from_here, base::OnceClosure task, base::TimeDelta delay);

// 单物理线程执行任务
void PostSingleThreadTask(const base::Location& from_here, base::OnceClosure task);
void PostSingleThreadDelayedTask(const base::Location& from_here, base::OnceClosure task, base::TimeDelta delay);

这里以PostTask为例表明是如何实现胶水层逻辑

代码语言:C++
复制
void ThreadPool::PostTask(Thread_Type type, const base::Location& from_here, base::OnceClosure task) {
    PostDelayedTask(type, from_here, std::move(task), base::TimeDelta());
}

void ThreadPool::PostDelayedTask(Thread_Type type, const base::Location& from_here, base::OnceClosure task, base::TimeDelta delay) {
    switch (type) {
    case Thread_Type::TYPE_MAIN_UI:
        PostDelayedUITask(from_here, std::move(WrapTask(type, from_here, std::move(task))), delay);
        break;
    case Thread_Type::TYPE_FILE_IO:
        m_spTaskRunnerFileIO->PostDelayedTask(from_here, std::move(WrapTask(type, from_here, std::move(task))), delay);
        break;
    case Thread_Type::TYPE_BACKGROUND:
        m_spTaskRunnerBackGround->PostDelayedTask(from_here, std::move(WrapTask(type, from_here, std::move(task))), delay);
        break;
    default:
        break;
    }
}

void ThreadPool::PostDelayedUITask(const base::Location& from_here, base::OnceClosure task, base::TimeDelta delay) {
    std::unique_ptr<base::PendingTask> newTask = std::make_unique<base::PendingTask>(from_here, std::move(task),
  base::TimeTicks::FromInternalValue(base::TimeTicks::Now().ToInternalValue() + delay.ToInternalValue()));
    m_upMessageUI->ScheduleDelayedWork(std::move(newTask));
}

主线程窗口循环

这里如果使用Qt框架,则无法使用Base库的RunLoop来接管主线程的窗口循环,所以没法直接实现异步任务调度到主线程执行,但是实际使用过程中,由于工作线程的异步任务执行完了后常常需要调度到主线程进行绘制界面,所以这里需要借助base::win::MessageWindow来实现主线程的调度,结合base::PendingTask来进行异步任务的调度

代码语言:C++
复制
base::win::MessageWindow m_hWindow;
m_hWindow.CreateNamed(base::BindRepeating(&MessageForUI::MessageCallback, base::Unretained(this)), std::wstring(L"WxDecryptMainWindow").c_str());

bool MessageForUI::MessageCallback(UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) {
    switch (message) {
    case kMsgHaveWork:
        HandleWorkMessage();
        break;
    case WM_TIMER:
        if (wparam == reinterpret_cast<UINT_PTR>(this))
            HandleTimerMessage();
        break;
    }
    return false;
}

这里对于MessageCallback的回调已经进入了主线程,调度任务的话具体可以参考base库的MessagePumpForUI类实现,分别实现ScheduleWork和ScheduleDelayedWork即可,其中延迟任务可以使用base::DelayedTaskQueue来进行管理,这里实际使用的优先队列来进行对任务的排序,每次消费任务保证消费的是最近需要执行延迟任务

代码语言:C++
复制
using DelayedTaskQueue = std::priority_queue<base::PendingTask>;
bool PendingTask::operator<(const PendingTask& other) const {
  // Since the top of a priority queue is defined as the "greatest" element, we
  // need to invert the comparison here.  We want the smaller time to be at the
  // top of the heap.

  if (delayed_run_time < other.delayed_run_time)
    return false;

  if (delayed_run_time > other.delayed_run_time)
    return true;

  // If the times happen to match, then we use the sequence number to decide.
  // Compare the difference to support integer roll-over.
  return (sequence_num - other.sequence_num) > 0;
}

初始化

这里初始化主要是线程池、TaskRunner、主线程窗口循环初始化

代码语言:C++
复制
// 根据CPU核数来初始化线程池最大线程数,最大程度利用机器性能
const int num_cores = base::SysInfo::NumberOfProcessors();
base::ThreadPoolInstance::InitParams initParams(std::max(3, num_cores - 1));
m_spThreadPool = std::make_shared<base::internal::ThreadPoolImpl>("Thread Pool");
// 初始化线程池工作线程观察者,方便监控线程的回收和创建
m_spObserver = std::make_shared<ThreadPoolObserver>();
m_spThreadPool->Start(initParams, std::dynamic_pointer_cast<base::WorkerThreadObserver>(m_spObserver).get());

// 创建IO线程TaskRunner
base::TaskTraits traits{ base::TaskPriority::USER_VISIBLE };
m_spTaskRunnerFileIO = m_spThreadPool->CreateTaskRunner(traits);

// 创建后台线程TaskRunner
base::TaskTraits traitsEffort{ base::TaskPriority::BEST_EFFORT };
m_spTaskRunnerBackGround = m_spThreadPool->CreateTaskRunner(traitsEffort);

// 创建顺序任务Runner,这里抛出的任务会顺序执行,但是并不保证在同一线程
base::TaskTraits traitsUIThread{ base::TaskPriority::BEST_EFFORT };
m_spSeqTaskRunner = m_spThreadPool->CreateSequencedTaskRunner(traitsUIThread);

// 创建单线程任务Runner,这里是实际的物理线程
m_spSingleTaskRunner = m_spThreadPool->CreateSingleThreadTaskRunner(traitsUIThread, base::SingleThreadTaskRunnerThreadMode::DEDICATED);

// 主线程窗口循环初始化
m_upMessageUI = std::make_unique<MessageForUI>();

带回复的异步任务

实现带回复的异步任务,主要参考base\task\post_task.h中的模版实现进行了进一步的封装

代码语言:C++
复制
template <template <typename> class CallbackType,
    typename TaskReturnType,
    typename ReplyArgType,
    typename = base::EnableIfIsBaseCallback<CallbackType>>
    bool ThreadPool::PostTaskAndReplyWithResult(Thread_Type type, const base::Location& from_here,
        CallbackType<TaskReturnType()> task,
        CallbackType<void(ReplyArgType)> reply) {
    auto* result = new std::unique_ptr<TaskReturnType>();
    return PostTaskAndReply(type, from_here,
        base::BindOnce(&base::internal::ReturnAsParamAdapter<TaskReturnType>, std::move(task),
            result),
        base::BindOnce(&base::internal::ReplyAdapter<TaskReturnType, ReplyArgType>,
            std::move(reply), base::Owned(result)));
}

实际调用的话,需要将task和reply的参数和返回值对应好,例如

代码语言:C++
复制
int readPool::AsyncTaskWithReturn() {
    return 1;
}

void ThreadPool::AsyncReplyWithParam(int i) {
    L_TRACE("i = [%d]", i);
}

Pool->PostTaskAndReplyWithResult(Thread_Type::TYPE_MAIN_UI, FROM_HERE,
  base::BindOnce(&WxThreadPool::AsyncTaskWithReturn, base::Unretained(Pool)),
  base::BindOnce(&WxThreadPool::AsyncReplyWithParam, base::Unretained(Pool)));

实现主线程的Reply的话,需要额外封装一层

代码语言:C++
复制
bool ThreadPool::PostTaskAndReplyOnMainUI(const base::Location& from_here, base::OnceClosure task, base::OnceClosure reply) {
    return PostTaskAndReply(Thread_Type::TYPE_BACKGROUND, from_here, std::move(task), base::BindOnce([](const base::Location& from_here, base::OnceClosure reply) {
        Pool->PostTask(Thread_Type::TYPE_MAIN_UI, from_here, std::move(reply));
        }, from_here, std::move(reply)));
}

任务监控

针对异步任务,希望实现全方位的监控,一方面是需要打开debug\debugging_buildflags.h中ENABLE_LOCATION_SOURCE来获取base::Location中更为详尽的信息,另外一方面,通过base::Time来实现任务耗时的精确计算,同样可以在胶水层进行实现

代码语言:C++
复制
PostDelayedUITask(from_here, std::move(task)), delay);

// 调整为

PostDelayedUITask(from_here, std::move(WrapTask(type, from_here, std::move(task))), delay);

将原有的task包裹一层处理即可

代码语言:C++
复制
base::OnceClosure WxThreadPool::WrapTask(Thread_Type type, const base::Location& from_here, base::OnceClosure task) {
    return base::BindOnce([](Thread_Type from_type, const base::Location& from_here, base::OnceClosure fromtask) {
        base::Time start_time = base::Time::Now();
        std::move(fromtask).Run();
        base::Time end_time = base::Time::Now();
        base::TimeDelta time_difference = end_time - start_time;
        double microseconds_difference = time_difference.InMicrosecondsF();
        L_TRACE("[%s]task location [%s] run cost [%.3f]ms", Util::TaskTypeToString(from_type).c_str(), from_here.ToString().c_str(), microseconds_difference / 1000);
        }, type, from_here, std::move(task));
}

包裹task需要注意移动语义,因为base库中异步任务的特殊性,必须使用右值来调用task的Run执行异步任务。

获取当前函数执行的详细堆栈以及同时并行的异步任务,可以使用宏封装好

代码语言:C++
复制
#define PRINT_DEBUG_INFO() \
  do { \
    base::debug::StackTrace stack_trace; \
    L_TRACE("stack_trace [%s]", stack_trace.ToString().c_str()); \
    base::debug::TaskTrace task_trace; \
    L_TRACE("task trace [%s]", task_trace.ToString().c_str()); \
  } while (0)

在任意异步任务实际执行函数头部调用宏即可。

代码语言:C++
复制
void TaskCenter::SomeTask() {
    // 输出堆栈信息
    PRINT_DEBUG_INFO();
}

PostTask(**,&TaskCenter::SomeTask, ***);

其他

单例模式

base库的单例模式使用较为简单,主要是依赖base::Singleton来实现全局的静态变量,注意这里使用的是懒汉模式,这意味着单例对象只有在第一次使用时才会被创建。使用base::Singleton只需将你的类作为模板参数传递给base::Singleton即可。

使用示例

代码语言:C++
复制
class MySingleton {
 public:
  // 获取单例对象的引用
  static MySingleton& GetInstance() {
    return base::Singleton<MySingleton>::get();
  }

  // 示例方法
  void DoSomething() {
    std::cout << "Doing something..."<< std::endl;
  }

 private:
  // 私有构造函数,防止外部构造
  MySingleton() {}

  // 禁止拷贝和赋值
  MySingleton(const MySingleton&) = delete;
  MySingleton& operator=(const MySingleton&) = delete;

  // 单例的析构函数
  friend struct base::DefaultSingletonTraits<MySingleton>;
  ~MySingleton() {}
};

int main() {
  MySingleton& singleton = MySingleton::GetInstance();
  singleton.DoSomething();
  return 0;
}

绑定策略

配合PostTask,常常需要将类成员变量当做异步任务,这时需要同步传入类实例对象来进行异步任务的调用,base::Bind提供了四种类实例绑定策略来实现各种场景下的异步任务的调用:Unretained、RetainedRef、Owned、Passed

  1. Unretained:当你使用Unretained时,绑定的对象不会被base::Bind持有。这意味着对象的生命周期必须比回调的生命周期长,否则在回调执行时对象可能已经被销毁,导致未定义行为。 使用场景:当你确定对象在回调执行时仍然有效时,可以使用Unretained。
  2. RetainedRef:RetainedRef策略确保base::Bind在内部保留对绑定对象的强引用,直到回调不再需要。这样可以保证在回调的生命周期内,对象不会被销毁。 使用场景:当你希望确保对象在回调执行期间保持有效时,可以使用RetainedRef。
  3. Owned:Owned策略意味着你将对象的所有权传递给base::Bind。base::Bind会在内部持有对象的指针,并在适当的时候负责销毁它。这通常用于自动管理对象生命周期的场景。 使用场景:当你希望base::Bind负责管理对象的生命周期,并且在回调执行完毕后自动销毁对象时,可以使用Owned。
  4. Passed:Passed策略允许你在回调执行时动态地传递对象。这通常用于需要延迟决定对象或在回调执行时才能确定对象的情况。 使用场景:当你希望在回调执行时传递对象,而不是在绑定时就确定对象时,可以使用Passed

基本的使用示例,仅做参考

代码语言:C++
复制
#include "base/bind.h"
#include "base/callback.h"
#include "base/run_loop.h"
#include<iostream>

class MyClass {
 public:
  void MyMethod(const std::string& message) {
    std::cout<< message<< std::endl;
  }
};

void MyFunction(const std::string& message) {
  std::cout<< message<< std::endl;
}

int main() {
  MyClass my_class;
  std::string message = "Hello, World!";

  // 使用Unretained
  base::OnceClosure unretained_callback = base::BindOnce(&MyClass::MyMethod, base::Unretained(&my_class), message);

  // 使用RetainedRef
  base::OnceClosure retained_ref_callback = base::BindOnce(&MyClass::MyMethod, base::RetainedRef(&my_class), message);

  // 使用Owned(需要手动管理对象的生命周期)
  std::unique_ptr<MyClass> my_class_owned(new MyClass());
  base::OnceClosure owned_callback = base::BindOnce(&MyClass::MyMethod, base::Owned(my_class_owned.get()), message);

  // 使用Passed
  base::OnceClosure passed_callback = base::BindOnce(&MyFunction, base::Passed(std::make_unique<std::string>(message)));

  // 执行回调
  base::RunLoop run_loop;
  run_loop.RunUntilIdle();

  return 0;
}

结语

本文以进程和线程两大基础模块来对base库进行一个实践应用,汇总了使用过程中的一些问题供读者参考,希望能起到抛砖引玉的作用。

谢谢各位看到这里,如果有感兴趣的模块或者代码需要攻略,也可以留言,会不定时更新。喜欢可以去github点点赞,再次感谢🙏

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 进程
    • 终止进程
      • 启动进程
        • 异同点
        • LaunchProcess
        • GetAppOutput
      • 遍历进程
        • 性能指标
        • 线程
          • 接口一览
            • 主线程窗口循环
              • 初始化
                • 带回复的异步任务
                  • 任务监控
                    • 其他
                      • 单例模式
                    • 绑定策略
                      • 结语
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档