当前位置:网站首页>Chromium Threading and Task
Chromium Threading and Task
2022-07-18 03:39:00 【~What's going on~】
This code is based on 101 stay Chromium in , There are not only famous Multi process architecture , There is also its own multi-threaded architecture with clear division of labor . stay Chromium In the multithreaded architecture of , In order to Task As the basic unit of scheduling .
Chromium Multithreading design idea
The main goal is to keep the main thread (a.k.a. “UI” thread in the browser process) and IO thread (each process’ thread for handling IPC) responsive. This means offloading any blocking I/O or other expensive operations to other threads. ( The main purpose is to make the main thread ( for example Browser In process UI Threads ) and IO Threads ( In process processing IPC The thread of the message ) Keep responding quickly . This means putting any I/O Blocking and other expensive operations , Put it in other threads to execute . It can be placed in devicated thread perhaps threadpool )
Locks are also an option to synchronize access but in Chrome we strongly prefer sequences to locks.( Lock is a choice to realize synchronous access , But in Chrome Inside , We prefer to use sequences, Not locks )
Most threads have a loop that gets tasks from a queue and runs them (the queue may be shared between multiple threads).( A large number of threads have one that can get tasks from the queue and execute them loop )
The type of thread
In a process , There are often the following threads :
One main thread
stay Browser In progress (BrowserThread::UI): Used to update the UI
stay Render In progress : function Blink
One io thread
stay Browser In progress (BrowserThread::IO): Used for processing IPC Messages and network requests
stay Render In progress : Used for processing IPC news
Some uses base::Tread Created , Threads for special purposes ( Possible )
Some threads generated when using thread pool ( Possible )
Be careful , stay Chromium, We should use base::Thread To create threads , Instead of specific under each platform platform thread
Task
task Encapsulates a base::OnceCloseure. We will pass TaskRunner hold task Put in a TaskQueue Inside , So that the corresponding method can be implemented all the time .
Task How to execute
The header file :https://source.chromium.org/chromium/chromium/src/+/main:base/task/task_executor.h;l=6?q=task_executor.h&sq=
TaskRunner
Task In parallel 、 Execute out of order , It is possible to execute all tasks on any thread at the same time ( Through thread pool base::TaskRunner)
The following code passes through TaskRunner PostTask Two tasks , They execute in parallel

#include <iostream>
#include <thread>
#include <unistd.h>
#include "base/run_loop.h"
#include "base/command_line.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/task/task_executor.h"
void doWork0(){
std::cout << "doWork0,tid=" << std::this_thread::get_id () << std::endl;
sleep(10);
std::cout << "doWork0, finish" << std::endl;
}
void doWork1(){
std::cout << "doWork1,tid=" << std::this_thread::get_id () << std::endl;
}
int main(int argc, const char* argv[]) {
base::CommandLine::Init(argc,argv);
base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Demo");
std::cout << "hello test task----------\n";
std::cout << "main,tid=" << std::this_thread::get_id () << std::endl;
//1 test SequencedTaskRunner
// scoped_refptr<base::SequencedTaskRunner> task_runner =
// base::ThreadPool::CreateSequencedTaskRunner(
// {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
// base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
// task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork0));
// task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork1));
scoped_refptr<base::TaskRunner> task_runner = base::ThreadPool::CreateTaskRunner({base::TaskPriority::USER_VISIBLE});
task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork0));
task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork1));
base::RunLoop loop;
loop.Run();
return 0;
}SequencedTaskRunner:
Task Will be executed in the order of joining , Execute a task on any thread at the same time (
SequencedTaskRunnerRealization ).Chrome The use of locks is discouraged . Sequences essentially provide thread safety . Prefer classes that are always accessed from the same sequence , Instead of using locks to manage your own thread safety . Thread safe but not thread affine ; How could this happen ? Tasks published to the same sequence will run sequentially . After a sorted task is completed , The next task may be picked up by another worker thread , But this task can ensure to see any side effects of the previous task on its sequence .
The following code passes through
SequencedTaskRunnerPostTask Two tasks , They will be executed in sequence , among doWork0 Sleeping 10s,doWork1 Need to wait until doWork0 Execute after execution

#include <iostream>
#include <thread>
#include <unistd.h>
#include "base/run_loop.h"
#include "base/command_line.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/thread_pool_instance.h"
void doWork0(){
std::cout << "doWork0,tid=" << std::this_thread::get_id () << std::endl;
sleep(10);
std::cout << "doWork0, finish" << std::endl;
}
void doWork1(){
std::cout << "doWork1,tid=" << std::this_thread::get_id () << std::endl;
}
int main(int argc, const char* argv[]) {
base::CommandLine::Init(argc,argv);
base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Demo");
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
std::cout << "hello test task----------\n";
std::cout << "main,tid=" << std::this_thread::get_id () << std::endl;
task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork0));
task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork1));
base::RunLoop loop;
loop.Run();
return 0;
}SingleSuqenceTaskRunner
Task Will be executed in the order of joining , Only one task is executed on a fixed thread at the same time ( adopt
SingleSuqenceTaskRunnerRealization )
If multiple tasks need to run on the same thread , And the thread does not have to be the main thread or IO Threads , Please publish them to Created SingleThreadTaskRunner.

#include <iostream>
#include <thread>
#include <unistd.h>
#include "base/run_loop.h"
#include "base/command_line.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/task/task_executor.h"
void doWork0(){
std::cout << "doWork0,tid=" << std::this_thread::get_id () << std::endl;
sleep(10);
std::cout << "doWork0, finish" << std::endl;
}
void doWork1(){
std::cout << "doWork1,tid=" << std::this_thread::get_id () << std::endl;
}
int main(int argc, const char* argv[]) {
base::CommandLine::Init(argc,argv);
base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Demo");
std::cout << "hello test task----------\n";
std::cout << "main,tid=" << std::this_thread::get_id () << std::endl;
//1 test SequencedTaskRunner
// scoped_refptr<base::TaskRunner> task_runner = base::ThreadPool::CreateTaskRunner({base::TaskPriority::USER_VISIBLE});
// task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork0));
// task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork1));
//2 test SequencedTaskRunner
// scoped_refptr<base::SequencedTaskRunner> task_runner =
// base::ThreadPool::CreateSequencedTaskRunner(
// {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
// base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
// task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork0));
// task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork1));
//3 test SingleThreadTaskRunner
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
base::ThreadPool::CreateSingleThreadTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork0));
task_runner->PostTask(FROM_HERE, base::BindOnce(&doWork1));
base::RunLoop loop;
loop.Run();
return 0;
}Posting a Task
https://source.chromium.org/chromium/chromium/src/+/main:base/task/task_runner.h;bpv=1;bpt=1
stay TaskRunner Class PostTask The way
// Equivalent to PostDelayedTask(from_here, task, 0).
bool PostTask(const Location& from_here, OnceClosure task);
// Like PostTask, but tries to run the posted task only after |delay_ms|
// has passed. Implementations should use a tick clock, rather than wall-
// clock time, to implement |delay|.
virtual bool PostDelayedTask(const Location& from_here,
OnceClosure task,
base::TimeDelta delay) = 0;
bool PostTaskAndReply(const Location& from_here,
OnceClosure task,
OnceClosure reply);
// Templating on the types of `task` and `reply` allows template matching to
// work for both base::RepeatingCallback and base::OnceCallback in each case.
template <typename TaskReturnType,
typename ReplyArgType,
template <typename>
class TaskCallbackType,
template <typename>
class ReplyCallbackType,
typename = EnableIfIsBaseCallback<TaskCallbackType>,
typename = EnableIfIsBaseCallback<ReplyCallbackType>>
bool PostTaskAndReplyWithResult(const Location& from_here,
TaskCallbackType<TaskReturnType()> task,
ReplyCallbackType<void(ReplyArgType)> reply) {
DCHECK(task);
DCHECK(reply);
// std::unique_ptr used to avoid the need of a default constructor.
auto* result = new std::unique_ptr<TaskReturnType>();
return PostTaskAndReply(
from_here,
BindOnce(&internal::ReturnAsParamAdapter<TaskReturnType>,
std::move(task), result),
BindOnce(&internal::ReplyAdapter<TaskReturnType, ReplyArgType>,
std::move(reply), Owned(result)));
}Posting to the Current Thread
// The task will run on the current thread in the future.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&Task));
// The task will run on the current (virtual) thread's default task queue.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&Task);#include <iostream>
#include <thread>
#include <unistd.h>
#include "base/run_loop.h"
#include "base/command_line.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/task/task_executor.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
void doWork0(){
std::cout << "doWork0,tid=" << std::this_thread::get_id () << std::endl;
sleep(1);
std::cout << "doWork0, finish" << std::endl;
}
void doWork1(){
std::cout << "doWork1,tid=" << std::this_thread::get_id () << std::endl;
}
void doWork2(){
std::cout << "doWork2,tid=" << std::this_thread::get_id () << std::endl;
sleep(2);
std::cout << "doWork2, finish" << std::endl;
}
void doWork3(){
std::cout << "doWork3,tid=" << std::this_thread::get_id () << std::endl;
}
int main(int argc, const char* argv[]) {
base::CommandLine::Init(argc,argv);
base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Demo");
std::cout << "hello test task----------\n";
std::cout << "main,tid=" << std::this_thread::get_id () << std::endl;
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&doWork0));
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&doWork1));
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&doWork2));
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&doWork3));
base::RunLoop loop;
loop.Run();
return 0;
}
Both methods are executed in the main thread
Be careful , In the use of base::BindOnce() Method to produce a base::OnceClosure When , Generally, a base::WeakPrt, Instead of a bare pointer .base::WeakPrt It can ensure that the pointed object is destroyed , Callbacks bound to objects can be canceled . Otherwise, a segment error will generally occur
OnceCallback<> and BindOnce(), RepeatingCallback<> and BindRepeating()
Definition :https://source.chromium.org/chromium/chromium/src/+/main:base/bind.h;l=6;drc=d5c694fc357d3b1cb40b3d40bb0c1e1ca15d952f?q=bind.h&sq=&ss=chromium%2Fchromium%2Fsrc
base::Callback<> and base::Bind() Their combination can provide a type safe callback method . You can bind some parameters to generate a function call . In this way, the function parameters of delayed execution can be passed . for example , stay chromium In the code, it is used to schedule tasks on different threads , Or in different MessageLoops Scheduling tasks .
When a callback function does not bind any parameters (base::Callback<void()>) Encapsulated into base::Closure. Be careful , This is different from other environments , It does not retain references to closures .
OnceCallback<> And RepeatingCallback<>
OnceCallback<> from base::BindOnce() establish , This return function variable is a move only type and can only be run once . By default , This will transfer binding parameters from internal storage to binding functions , Therefore, it is easier to use with movable types . A good feature of callback functions is : The declaration cycle is very clear , Therefore, it is easier to determine when calls between threads are destroyed .
base::RepeatingCallback<> from base::BindRepeating() establish , Obviously, this can be called repeatedly , Therefore, it is difficult to judge whether it is destroyed .
// |Foo| just refers to |cb| but doesn't store it nor consume it.
bool Foo(const base::OnceCallback<void(int)>& cb) {
return cb.is_null();
}
// |Bar| takes the ownership of |cb| and stores |cb| into |g_cb|.
base::RepeatingCallback<void(int)> g_cb;
void Bar(base::RepeatingCallback<void(int)> cb) {
g_cb = std::move(cb);
}
// |Baz| takes the ownership of |cb| and consumes |cb| by Run().
void Baz(base::OnceCallback<void(int)> cb) {
std::move(cb).Run(42);
}
// |Qux| takes the ownership of |cb| and transfers ownership to PostTask(),
// which also takes the ownership of |cb|.
void Qux(base::RepeatingCallback<void(int)> cb) {
PostTask(FROM_HERE, base::BindOnce(cb, 42));
PostTask(FROM_HERE, base::BindOnce(std::move(cb), 43));
}
If you don't need to save references, use std::move() Pass values to this callback function , Otherwise, directly pass the object .
Binding A Class Method
#include <iostream>
#include <thread>
#include <unistd.h>
#include "base/run_loop.h"
#include "base/command_line.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/task/task_executor.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
class Ref : public base::RefCountedThreadSafe<Ref> {
public:
Ref(){}
Ref(const Ref&) = delete;
Ref& operator=(const Ref&) = delete;
void Foo(int num) { std::cout << num << "-Foo\n"; }
private:
friend class RefCountedThreadSafe<Ref>;
~Ref();
};
Ref::~Ref(){
std::cout << "Ref::~Ref()\n";
}
void test(){
scoped_refptr<Ref> ref = new Ref();
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&Ref::Foo, ref,1));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::BindOnce(&Ref::Foo, ref,2),base::Milliseconds(1000));
}
int main(int argc, const char* argv[]) {
base::CommandLine::Init(argc,argv);
base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Demo");
std::cout << "hello test task----------\n";
std::cout << "main,tid=" << std::this_thread::get_id () << std::endl;
test();
base::RunLoop loop;
loop.Run();
return 0;
}
RefCountedThreadSafe and scoped_refptr Use https://blog.51cto.com/xiamachao/2461171
When PostDelayedTask After the execution ,ref References to are 0,d Call destructor .
If in test Use raw pointers in functions ,Ref* ref = new Ref(); Here's the picture , Will cause memory leaks
void test1(){
Ref *ref = new Ref();
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindOnce(&Ref::Foo, base::Unretained(ref),1));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::BindOnce(&Ref::Foo, base::Unretained(ref),2),base::Milliseconds(1000));
}base::Unretained It means Task The existence of the object is not guaranteed , By the caller of the publishing task , Ensure that the callback is executed ,this The pointer is still available . In the use of base::Unretained structure Task Caution is required when , Because it does not increase the reference count of the object , Therefore, it must be ensured that Task The object will not be destroyed before execution , Otherwise, it is easy to base::Callback::Run Cause collapse inside ( But it may not be encountered during debugging ). If you can't ensure that the object is Task And then destroy , It's best to change the object from base::RefCountedThreadSafe Inherit , stay base::Bind Use bare pointer or scoped_refptr. If it is sent to the current thread Task have access to WeakPtr.
PostTaskAndReplyWithResult
#include <iostream>
#include <thread>
#include <unistd.h>
#include "base/run_loop.h"
#include "base/command_line.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/task/task_executor.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
class Ref : public base::RefCountedThreadSafe<Ref> {
public:
Ref(){}
Ref(const Ref&) = delete;
Ref& operator=(const Ref&) = delete;
void Foo(int num) { std::cout << num << "-Foo\n"; }
int add(int left,int right){
std::cout << "set:" << left << "+" << right << "\n";
sleep(10);
return left + right;
}
void set(int v){
std::cout << "set:" << v << "\n";
}
private:
friend class RefCountedThreadSafe<Ref>;
~Ref();
};
Ref::~Ref(){
std::cout << "Ref::~Ref()\n";
}
void test2(){
/*
/ PostTaskAndReplyWithResult The meaning of
// Parameters 1 Is where the code is executed
// Parameters 2 It's a closure A, There is a return value ( There is no limit to entry ) Such as int add(int left,int right)
// Parameters 3 It's a closure B, There are multiple inputs , no return value Such as void set(int v)
// Closure A When Binding , Fill in all the entries
// Closure B When Binding , Fill in all the input parameters in the front , The last input parameter is the closure A The execution result of
// Execution effect :
// First deliver the task to the task queue
// The task queue takes out the task for execution
// Execute the closure first A, Get the result back A1
// Then execute the closure B, take A1 As a closure B The last input of
// Closure B Execution completed
// Task execution completed
*/
scoped_refptr<Ref> ref = new Ref();
base::ThreadTaskRunnerHandle::Get()->PostTaskAndReplyWithResult(FROM_HERE,
base::BindOnce(&Ref::add,ref,1,2),
base::BindOnce(&Ref::set,ref));
}
int main(int argc, const char* argv[]) {
base::CommandLine::Init(argc,argv);
base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Demo");
std::cout << "hello test task----------\n";
std::cout << "main,tid=" << std::this_thread::get_id () << std::endl;
test2();
base::RunLoop loop;
loop.Run();
return 0;
}
Underlying principle
As can be seen from the above code , We can use all kinds of TaskRunner The subclass of post task. that task How to be dispatched , Running ? The following figure is a specific class diagram .

MessageLoop
The entry of the whole message cycle , Will the initialization MessagePump、SequenceManager、TaskQueue Objects such as , And call it BindToCurrentThread Method . commonly base::Thread There will be one MessageLoopCurrent example . But because the main thread is not by base::Thread type , So you have to declare one manually MessageLoop.
MessagePump
seeing the name of a thing one thinks of its function ,MessagePump It's a message pump , Cycle through the messages associated with it ( It bears the function of message circulation ). The classic message cycle is as follows :
for (;;) {
bool did_work = DoInternalWork();
if (should_quit_)
break;
did_work |= delegate_->DoWork();
if (should_quit_)
break;
TimeTicks next_time;
did_work |= delegate_->DoDelayedWork(&next_time);
if (should_quit_)
break;
if (did_work)
continue;
did_work = delegate_->DoIdleWork();
if (should_quit_)
break;
if (did_work)
continue;
WaitForWork();
}ThreadController
Get the to be scheduled task, And pass it on to TaskAnnotator. ThreadController::SechuleWork() Method from SequencdTaskSource::SelectNextTask Get the next one to be transferred task, And call TaskAnnotator::RunTask() Method executes the task
SequencedTaskSource
by ThreadControll Provide to be dispatched task, This task From TaskQueueSelector::SelectWorkQueueToService()
TaskQueueSelector
TaskQueueSelector From the current TaskQueue Medium immediate_work_queue and delayed_incoming_queue Get the corresponding task
TaskQueue
Task Save in TaskQueue Inside , TaskQueue In the specific implementation of , There are four queues :
Immediate (non-delayed) tasks:
immediate_incoming_queue - PostTask enqueues tasks here.
immediate_work_queue - SequenceManager takes immediate tasks here.
Delayed tasks
delayed_incoming_queue - PostDelayedTask enqueues tasks here.
delayed_work_queue - SequenceManager takes delayed tasks here.
When immediate_work_queue When it's empty ,immediate_work_queue Hui He immediate_incoming_queue swapping .delayed task Will be preserved first delayed_incoming_queue, Then there will be a name TimeDomain class , It will be time delayed task Add to delayed_work_queue in .
The flow chart of the above process is shown as follows :
Reference documents
https://chromium.googlesource.com/chromium/src/+/HEAD/docs/callback.md
https://chromium.googlesource.com/chromium/src/+/HEAD/docs/threading_and_tasks.md
https://blog.csdn.net/Summer__show_/article/details/102807329
边栏推荐
- BufferedInputStream字节缓存输入流和缓存流的效率测试_复制文件
- 腾讯T4架构师带你“一窥”大型网站架构的主要技术挑战和解决方案
- 在线办公,如何让协同更高效?
- Go zero micro service practical series (v. how to write cache code)
- Is it true that double non undergraduate students cannot enter the big factory? Ali technology four sides + cross face +hr face, successfully got the offer!!
- Data transmission: Practice of batch extraction of isomorphic and heterogeneous IP data sources
- Thesis learning (I) -- MWP bert: numerical augmented pre training for math wordproblem solving
- PD-Server GRPC 接口图解
- DEVKIT-mpc5744p配置rtos
- 世界首款抗量子攻击商用密码芯片 | 沐创
猜你喜欢

Efficient development of harmonyos course applications based on ETS

C# 使用ToolTip控件实现气泡提示

Go zero micro service practical series (v. how to write cache code)

Once, I sprayed AI customer service for two minutes, and it only replied to my first sentence

The difference between B tree and b+ tree

四面阿里offer定级P8,2022最新最实用阿里68道高级面试题,助你们面试成功!!

Really cow b! JD t3-2 is still learning microservice +mysql+kafka+boot2 X+ virtual machine pdf

The latest research of Zhu Songchun's team: robots can "confide" with humans! Also said that the next step is to create "Ai white"

Wrap in shutter

两年CRUD,普通二本毕业,挑战三个月面试阿里,成功拿下offer定级P7!年薪50w
随机推荐
虚实之间03 | 有这些技术,你就是让数字人活起来的“神笔马良”
leetcode:558. 四叉树交集【四叉树dfs】
Redis connection pool
Two years ago, how were the leading players and blue chips in defi?
[visdom drawing] summary of visdom drawing in deep learning
Chromium Threading and Task
第106期:HREE.JS的应用场景和基本概念
Wrap in shutter
难道双非本科就一定进不了大厂?阿里技术四面+交叉面+HR面,成功拿到offer!!
Is it true that double non undergraduate students cannot enter the big factory? Ali technology four sides + cross face +hr face, successfully got the offer!!
win11虚拟机里面mysql的ibd文件在哪里
软考 系统架构设计师 简明教程 | 企业信息化与电子商务
中国人力资源数字化生态图谱-灵活用工市场
Google Earth Engine(GEE)——S2影像异常值
腾讯T4架构师带你“一窥”大型网站架构的主要技术挑战和解决方案
00后博士毕业拟任南大特任副研究员,网友扒出论文后吵翻了
McKinsey: in the next decade, the top ten technology trends will affect investment and research direction
Accenture's 22 year technology outlook report: digital transformation will usher in the next decade
[200 opencv routines] 230 LBP statistical histogram of feature description
Clickpaas Ma Jun: model driven low code platform practice