并行编程实战——TBB中节点的数据结构
一、节点的定义
在前面分析过了节点相关的应用和功能,也在其中分析过一些节点的数据定义情况。本文就对节点的数据定义进行一个更详细具体的分析说明,特别是对一些应用上的细节展开说明一下。知其然,然后知其所以然。
节点的定义,基本都是以模板进行的。所以希望大家有一些基本的模板的知识,否则看起来还是有些麻烦的。官方文档这样说:“A node is a class that inherits from oneapi::tbb::flow::graph_node and also typically inherits from oneapi::tbb::flow::sender , oneapi::tbb::flow::receiver or both. ”。可以理解为在TBB中创建节点一般是继承自上面的所说的三个数据结构的定义。当然,在TBB中已经定义了好几种节点类型,一般情况下,是不需要自己主动去自定义自己的节点类型了。
节点的数据结构,一般分为三块:
template< typename Body> function_node(graph &g, size_t concurrency, Body body)
也就是图,并行度和不同类型的执行体。图主要是用来限制节点的应用范围,即这个节点在哪个图中进行工作;concurrency用来限制节点并发调用的次数,可以是1(1代表串行)~N次;而Body则表示开发者定义的消息处理函数,它可以是函数、Lambda表达式等。通过这三项,基本就可以完成整个TBB节点的工作处理。
二、基本数据结构
下面先看一下基础的三个类型节点的定义中的第一个graph_node:
//! The base of all graph nodes.
class graph_node : no_copy {friend class graph;template<typename C, typename N>friend class graph_iterator;#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SETfriend class get_graph_helper;
#endifprotected:graph& my_graph;graph& graph_reference() const {// TODO revamp: propagate graph_reference() method to all the reference places.return my_graph;}graph_node* next = nullptr;graph_node* prev = nullptr;
public:explicit graph_node(graph& g);virtual ~graph_node();protected:// performs the reset on an individual node.virtual void reset_node(reset_flags f = rf_reset_protocol) = 0;
}; // class graph_node
graph_node在上面的注释中已经说明了,其是TBB图中所有节点的基类。所以看它的实际定义其实非常简单,除了图相关的友元和变量外,只有几个节点的前驱和后继指针。其显示的构造函数要求必须输入一个图的数据对象。这也符合基类是一个抽象的设计。
再看一下相关的sender:
//! Pure virtual template class that defines a sender of messages of type T
template< typename T >
class sender {
public:virtual ~sender() {}//! Request an item from the sendervirtual bool try_get( T & ) { return false; }#if __TBB_PREVIEW_FLOW_GRAPH_TRY_PUT_AND_WAITvirtual bool try_get( T &, message_metainfo& ) { return false; }
#endif//! Reserves an item in the sendervirtual bool try_reserve( T & ) { return false; }#if __TBB_PREVIEW_FLOW_GRAPH_TRY_PUT_AND_WAITvirtual bool try_reserve( T &, message_metainfo& ) { return false; }
#endif//! Releases the reserved itemvirtual bool try_release( ) { return false; }//! Consumes the reserved itemvirtual bool try_consume( ) { return false; }protected://! The output type of this sendertypedef T output_type;//! The successor type for this nodetypedef receiver<T> successor_type;//! Add a new successor to this nodevirtual bool register_successor( successor_type &r ) = 0;//! Removes a successor from this nodevirtual bool remove_successor( successor_type &r ) = 0;template<typename C>friend bool register_successor(sender<C>& s, receiver<C>& r);template<typename C>friend bool remove_successor (sender<C>& s, receiver<C>& r);
}; // class sender<T>
上面的类注释也说明了,它是一个发送消息的纯虚拟的模板类。而在TBB中,节点的一个重要的功能就是能够收发消息,而消息的收发,又受限于图的约束(边和节点)。既然是一个纯虚类,那它就是一个接口类。这也符合消息传递的接口性的设计原则。在其中可以看到消息处理的函数接口如try_get等 。而与其相类似的是receiver:
//! Pure virtual template class that defines a receiver of messages of type T
template< typename T >
class receiver {
private:template <typename... TryPutTaskArgs>bool internal_try_put(const T& t, TryPutTaskArgs&&... args) {graph_task* res = try_put_task(t, std::forward<TryPutTaskArgs>(args)...);if (!res) return false;if (res != SUCCESSFULLY_ENQUEUED) spawn_in_graph_arena(graph_reference(), *res);return true;}public://! Destructorvirtual ~receiver() {}//! Put an item to the receiverbool try_put( const T& t ) {return internal_try_put(t);}#if __TBB_PREVIEW_FLOW_GRAPH_TRY_PUT_AND_WAIT//! Put an item to the receiver and wait for completionbool try_put_and_wait( const T& t ) {// Since try_put_and_wait is a blocking call, it is safe to create wait_context on stackd1::wait_context_vertex msg_wait_vertex{};bool res = internal_try_put(t, message_metainfo{message_metainfo::waiters_type{&msg_wait_vertex}});if (res) {__TBB_ASSERT(graph_reference().my_context != nullptr, "No wait_context associated with the Flow Graph");wait(msg_wait_vertex.get_context(), *graph_reference().my_context);}return res;}
#endif//! put item to successor; return task to run the successor if possible.
protected://! The input type of this receivertypedef T input_type;//! The predecessor type for this nodetypedef sender<T> predecessor_type;template< typename R, typename B > friend class run_and_put_task;template< typename X, typename Y > friend class broadcast_cache;template< typename X, typename Y > friend class round_robin_cache;virtual graph_task *try_put_task(const T& t) = 0;
#if __TBB_PREVIEW_FLOW_GRAPH_TRY_PUT_AND_WAITvirtual graph_task *try_put_task(const T& t, const message_metainfo&) = 0;
#endifvirtual graph& graph_reference() const = 0;template<typename TT, typename M> friend class successor_cache;virtual bool is_continue_receiver() { return false; }// TODO revamp: reconsider the inheritance and move node priority out of receivervirtual node_priority_t priority() const { return no_priority; }//! Add a predecessor to the nodevirtual bool register_predecessor( predecessor_type & ) { return false; }//! Remove a predecessor from the nodevirtual bool remove_predecessor( predecessor_type & ) { return false; }template <typename C>friend bool register_predecessor(receiver<C>& r, sender<C>& s);template <typename C>friend bool remove_predecessor (receiver<C>& r, sender<C>& s);
}; // class receiver<T>
在下面的try_put函数中可以看到TBB内部的处理过程:
//! Put an item to the receiverbool try_put( const T& t ) {return internal_try_put(t);}template <typename... TryPutTaskArgs>bool internal_try_put(const T& t, TryPutTaskArgs&&... args) {graph_task* res = try_put_task(t, std::forward<TryPutTaskArgs>(args)...);if (!res) return false;if (res != SUCCESSFULLY_ENQUEUED) spawn_in_graph_arena(graph_reference(), *res);return true;}
看明白了这三个基础的数据结构也就明白了为什么官网说都要从这三个类中继承。主要原因就在于一个节点中抛开图的相关内容后,关于节点的相关控制和消息处理,这三个类或多或少的都有。也就是说,除非完全手动创造代码,那最简单方便的就是从这三个类继承一下。
三、节点预定义数据结构
1、输入节点
//! An executable node that acts as a source, i.e. it has no predecessorstemplate < typename Output >__TBB_requires(std::copyable<Output>)
class input_node : public graph_node, public sender< Output > {
public://! The type of the output message, which is completetypedef Output output_type;//! The type of successors of this nodetypedef typename sender<output_type>::successor_type successor_type;// Input node has no input typetypedef null_type input_type;//! Constructor for a node with a successortemplate< typename Body >__TBB_requires(input_node_body<Body, Output>)__TBB_NOINLINE_SYM input_node( graph &g, Body body ): graph_node(g), my_active(false), my_body( new input_body_leaf< output_type, Body>(body) ), my_init_body( new input_body_leaf< output_type, Body>(body) ), my_successors(this), my_reserved(false), my_has_cached_item(false){fgt_node_with_body(CODEPTR(), FLOW_INPUT_NODE, &this->my_graph,static_cast<sender<output_type> *>(this), this->my_body);}#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SETtemplate <typename Body, typename... Successors>__TBB_requires(input_node_body<Body, Output>)input_node( const node_set<order::preceding, Successors...>& successors, Body body ): input_node(successors.graph_reference(), body){make_edges(*this, successors);}
#endif//! Copy constructor__TBB_NOINLINE_SYM input_node( const input_node& src ): graph_node(src.my_graph), sender<Output>(), my_active(false), my_body(src.my_init_body->clone()), my_init_body(src.my_init_body->clone()), my_successors(this), my_reserved(false), my_has_cached_item(false){fgt_node_with_body(CODEPTR(), FLOW_INPUT_NODE, &this->my_graph,static_cast<sender<output_type> *>(this), this->my_body);}//! The destructor~input_node() { delete my_body; delete my_init_body; }//! Add a new successor to this nodebool register_successor( successor_type &r ) override {spin_mutex::scoped_lock lock(my_mutex);my_successors.register_successor(r);if ( my_active )spawn_put();return true;}//! Removes a successor from this nodebool remove_successor( successor_type &r ) override {spin_mutex::scoped_lock lock(my_mutex);my_successors.remove_successor(r);return true;}//! Request an item from the nodebool try_get( output_type &v ) override {spin_mutex::scoped_lock lock(my_mutex);if ( my_reserved )return false;if ( my_has_cached_item ) {v = my_cached_item;my_has_cached_item = false;return true;}// we've been asked to provide an item, but we have none. enqueue a task to// provide one.if ( my_active )spawn_put();return false;}//! Reserves an item.bool try_reserve( output_type &v ) override {spin_mutex::scoped_lock lock(my_mutex);if ( my_reserved ) {return false;}if ( my_has_cached_item ) {v = my_cached_item;my_reserved = true;return true;} else {return false;}}#if __TBB_PREVIEW_FLOW_GRAPH_TRY_PUT_AND_WAIT
private:bool try_reserve( output_type& v, message_metainfo& ) override {return try_reserve(v);}bool try_get( output_type& v, message_metainfo& ) override {return try_get(v);}
public:
#endif//! Release a reserved item./** true = item has been released and so remains in sender, dest must request or reserve future items */bool try_release( ) override {spin_mutex::scoped_lock lock(my_mutex);__TBB_ASSERT( my_reserved && my_has_cached_item, "releasing non-existent reservation" );my_reserved = false;if(!my_successors.empty())spawn_put();return true;}//! Consumes a reserved itembool try_consume( ) override {spin_mutex::scoped_lock lock(my_mutex);__TBB_ASSERT( my_reserved && my_has_cached_item, "consuming non-existent reservation" );my_reserved = false;my_has_cached_item = false;if ( !my_successors.empty() ) {spawn_put();}return true;}//! Activates a node that was created in the inactive statevoid activate() {spin_mutex::scoped_lock lock(my_mutex);my_active = true;if (!my_successors.empty())spawn_put();}template<typename Body>Body copy_function_object() {input_body<output_type> &body_ref = *this->my_body;return dynamic_cast< input_body_leaf<output_type, Body> & >(body_ref).get_body();}protected://! resets the input_node to its initial statevoid reset_node( reset_flags f) override {my_active = false;my_reserved = false;my_has_cached_item = false;if(f & rf_clear_edges) my_successors.clear();if(f & rf_reset_bodies) {input_body<output_type> *tmp = my_init_body->clone();delete my_body;my_body = tmp;}}private:spin_mutex my_mutex;bool my_active;input_body<output_type> *my_body;input_body<output_type> *my_init_body;broadcast_cache< output_type > my_successors;bool my_reserved;bool my_has_cached_item;output_type my_cached_item;// used by apply_body_bypass, can invoke body of node.bool try_reserve_apply_body(output_type &v) {spin_mutex::scoped_lock lock(my_mutex);if ( my_reserved ) {return false;}if ( !my_has_cached_item ) {d1::flow_control control;fgt_begin_body( my_body );my_cached_item = (*my_body)(control);my_has_cached_item = !control.is_pipeline_stopped;fgt_end_body( my_body );}if ( my_has_cached_item ) {v = my_cached_item;my_reserved = true;return true;} else {return false;}}graph_task* create_put_task() {d1::small_object_allocator allocator{};typedef input_node_task_bypass< input_node<output_type> > task_type;graph_task* t = allocator.new_object<task_type>(my_graph, allocator, *this);return t;}//! Spawns a task that applies the bodyvoid spawn_put( ) {if(is_graph_active(this->my_graph)) {spawn_in_graph_arena(this->my_graph, *create_put_task());}}friend class input_node_task_bypass< input_node<output_type> >;//! Applies the body. Returning SUCCESSFULLY_ENQUEUED okay; forward_task_bypass will handle it.graph_task* apply_body_bypass( ) {output_type v;if ( !try_reserve_apply_body(v) )return nullptr;graph_task *last_task = my_successors.try_put_task(v);if ( last_task )try_consume();elsetry_release();return last_task;}
}; // class input_node
输入节点比较复杂一些,但仔细看其中的代码,其实没有什么特别的,其实具体到最后就是任务的处理了。毕竟其属于对消息的接收处理。
2、功能节点
//! Implements a function node that supports Input -> Output
template<typename Input, typename Output = continue_msg, typename Policy = queueing>__TBB_requires(std::default_initializable<Input> &&std::copy_constructible<Input> &&std::copy_constructible<Output>)
class function_node: public graph_node, public function_input< Input, Output, Policy, cache_aligned_allocator<Input> >, public function_output<Output>
{typedef cache_aligned_allocator<Input> internals_allocator;public:typedef Input input_type;typedef Output output_type;typedef function_input<input_type,output_type,Policy,internals_allocator> input_impl_type;typedef function_input_queue<input_type, internals_allocator> input_queue_type;typedef function_output<output_type> fOutput_type;typedef typename input_impl_type::predecessor_type predecessor_type;typedef typename fOutput_type::successor_type successor_type;using input_impl_type::my_predecessors;//! Constructor// input_queue_type is allocated here, but destroyed in the function_input_base.// TODO: pass the graph_buffer_policy to the function_input_base so it can all// be done in one place. This would be an interface-breaking change.template< typename Body >__TBB_requires(function_node_body<Body, Input, Output>)__TBB_NOINLINE_SYM function_node( graph &g, size_t concurrency,Body body, Policy = Policy(), node_priority_t a_priority = no_priority ): graph_node(g), input_impl_type(g, concurrency, body, a_priority),fOutput_type(g) {fgt_node_with_body( CODEPTR(), FLOW_FUNCTION_NODE, &this->my_graph,static_cast<receiver<input_type> *>(this), static_cast<sender<output_type> *>(this), this->my_body );}template <typename Body>__TBB_requires(function_node_body<Body, Input, Output>)function_node( graph& g, size_t concurrency, Body body, node_priority_t a_priority ): function_node(g, concurrency, body, Policy(), a_priority) {}#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SETtemplate <typename Body, typename... Args>__TBB_requires(function_node_body<Body, Input, Output>)function_node( const node_set<Args...>& nodes, size_t concurrency, Body body,Policy p = Policy(), node_priority_t a_priority = no_priority ): function_node(nodes.graph_reference(), concurrency, body, p, a_priority) {make_edges_in_order(nodes, *this);}template <typename Body, typename... Args>__TBB_requires(function_node_body<Body, Input, Output>)function_node( const node_set<Args...>& nodes, size_t concurrency, Body body, node_priority_t a_priority ): function_node(nodes, concurrency, body, Policy(), a_priority) {}
#endif // __TBB_PREVIEW_FLOW_GRAPH_NODE_SET//! Copy constructor__TBB_NOINLINE_SYM function_node( const function_node& src ) :graph_node(src.my_graph),input_impl_type(src),fOutput_type(src.my_graph) {fgt_node_with_body( CODEPTR(), FLOW_FUNCTION_NODE, &this->my_graph,static_cast<receiver<input_type> *>(this), static_cast<sender<output_type> *>(this), this->my_body );}protected:template< typename R, typename B > friend class run_and_put_task;template<typename X, typename Y> friend class broadcast_cache;template<typename X, typename Y> friend class round_robin_cache;using input_impl_type::try_put_task;broadcast_cache<output_type> &successors () override { return fOutput_type::my_successors; }void reset_node(reset_flags f) override {input_impl_type::reset_function_input(f);// TODO: use clear() instead.if(f & rf_clear_edges) {successors().clear();my_predecessors.clear();}__TBB_ASSERT(!(f & rf_clear_edges) || successors().empty(), "function_node successors not empty");__TBB_ASSERT(this->my_predecessors.empty(), "function_node predecessors not empty");}}; // class function_node
功能节点中可以看一看function_input等几个类似的类,做为单输入输出的节点,其实麻烦就在于拿到消息后要根据策略来进行处理。
3、输出节点
//! Forwards messages of type T to all successors
template <typename T>
class broadcast_node : public graph_node, public receiver<T>, public sender<T> {
public:typedef T input_type;typedef T output_type;typedef typename receiver<input_type>::predecessor_type predecessor_type;typedef typename sender<output_type>::successor_type successor_type;
private:broadcast_cache<input_type> my_successors;
public:__TBB_NOINLINE_SYM explicit broadcast_node(graph& g) : graph_node(g), my_successors(this) {fgt_node( CODEPTR(), FLOW_BROADCAST_NODE, &this->my_graph,static_cast<receiver<input_type> *>(this), static_cast<sender<output_type> *>(this) );}#if __TBB_PREVIEW_FLOW_GRAPH_NODE_SETtemplate <typename... Args>broadcast_node(const node_set<Args...>& nodes) : broadcast_node(nodes.graph_reference()) {make_edges_in_order(nodes, *this);}
#endif// Copy constructor__TBB_NOINLINE_SYM broadcast_node( const broadcast_node& src ) : broadcast_node(src.my_graph) {}//! Adds a successorbool register_successor( successor_type &r ) override {my_successors.register_successor( r );return true;}//! Removes s as a successorbool remove_successor( successor_type &r ) override {my_successors.remove_successor( r );return true;}private:graph_task* try_put_task_impl(const T& t __TBB_FLOW_GRAPH_METAINFO_ARG(const message_metainfo& metainfo)) {graph_task* new_task = my_successors.try_put_task(t __TBB_FLOW_GRAPH_METAINFO_ARG(metainfo));if (!new_task) new_task = SUCCESSFULLY_ENQUEUED;return new_task;}protected:template< typename R, typename B > friend class run_and_put_task;template<typename X, typename Y> friend class broadcast_cache;template<typename X, typename Y> friend class round_robin_cache;//! build a task to run the successor if possible. Default is old behavior.graph_task* try_put_task(const T& t) override {return try_put_task_impl(t __TBB_FLOW_GRAPH_METAINFO_ARG(message_metainfo{}));}#if __TBB_PREVIEW_FLOW_GRAPH_TRY_PUT_AND_WAITgraph_task* try_put_task(const T& t, const message_metainfo& metainfo) override {return try_put_task_impl(t, metainfo);}
#endifgraph& graph_reference() const override {return my_graph;}void reset_node(reset_flags f) override {if (f&rf_clear_edges) {my_successors.clear();}__TBB_ASSERT(!(f & rf_clear_edges) || my_successors.empty(), "Error resetting broadcast_node");}
}; // broadcast_node
而广播节点则更容易为开发者理解,它其实就是将消息转发的一个节点。不做缓存,这和实际的广播也是相同的。广播完成,消息也就没了。
当然,除了上面的三种节点,在TBB中还有不少的节点类型,这里只截取这三种比较有代表性的来给大家分析一下。其实通过这些节点的类型分析,是不是考虑可以更抽象一层的对其进行封装,而不是简单的应用这些节点类型。让这些节点被抽象到更高层后,可能应用会更方便,也可能更符合当前的实际应用场景。
四、总结
源码之前,了无秘密。这句话太实在了。一个高手的再好的设计再牛的编码,最终都得体现到代码中去。否则,这种思想的体现就无法实现,而无法实现的思想,至少在实践这个角度上,就无法让大多数人学会。计算机技术的特点就是如此,代码就是王道。
相关文章:
并行编程实战——TBB中节点的数据结构
一、节点的定义 在前面分析过了节点相关的应用和功能,也在其中分析过一些节点的数据定义情况。本文就对节点的数据定义进行一个更详细具体的分析说明,特别是对一些应用上的细节展开说明一下。知其然,然后知其所以然。 节点的定义,…...
ClickHouse总结
背景 OLAP(联机分析处理) 是一种用于在大规模数据集上进行复杂分析的数据处理方法。与OLTP(联机事务处理)系统专注于支持日常业务交易和操作不同,OLAP系统旨在提供对多维数据的快速、灵活的查询和分析能力。 OLAP场景…...
Guava中Preconditions校验
Guava中Preconditions校验 场景引入Guava 参数校验 Preconditionspom 依赖引入常用的方法 场景引入 提出疑问?为什么不直接使用 jsr330校验注解对实体类进行校验呢? 答:不同的场景,如短信码验证登录,账号密码登录此类…...
容器技术--Docker常用命令
Docker常用命令 镜像的命令 # 查看本地所有镜像 docker images # 向服务端发送请求,服务端处理 # 只获取镜像id docker images -q # 镜像管理 docker image# 查看镜像的详细信息 docker image inspect 镜像id # 查看 容器整体信息 docker info | grep -iE...

【Linux】网络层协议——IP
一、IP协议 在前面,我们学习了应用层和传输层,接下来,我们来学习网络层,网络层的主要功能是在复杂的网络环境中确定一个合适的路由。 1.1 IP协议的基本概念 主机:配有IP地址,有可以进行路由控制的设备路由…...

【Echarts】vue3打开echarts的正确方式
ECharts 是一个功能强大、灵活易用的数据可视化工具,适用于商业报表、数据分析、科研教育等多种场景。那么该如何优雅的使用Echarts呢? 这里以vue3为例。 安装echarts pnpm i echarts封装公用方法 // ts-nocheck import * as echarts from echarts; // 我们这里借…...
一些学习three的小记录
这篇主要用来记录我学习3d渲染相关的疑问记录,后续会持续的更新,如果我的理解不对欢迎评论区更正。 目录 1.WebGLRenderer和WebGPURenderer的区别 1.1 WebGLRenderer 1.2 WebGPURenderer 二、scene.background和renderer.setClearColor有什么区别 三、renderer.setAnimat…...

Porcupine - 语音关键词唤醒引擎
文章目录 一、关于 Porcupine特点用例尝试一下 语言支持性能 二、Demo1、Python Demo2、iOS DemoBackgroundService DemoForegroundApp Demo 3、网页 Demo3.1 Vanilla JavaScript 和 HTML3.2 Vue Demos 三、SDK - Python 一、关于 Porcupine Porcupine 是一个高度准确和轻量级…...

Golang | Leetcode Golang题解之第409题最长回文串
题目: 题解: func longestPalindrome(s string) int {mp : map[byte]int{}for i : 0; i < len(s); i {mp[s[i]]}res : 0for _, v : range mp {if v&1 1 {res v - 1} else {res v}}if res<len(s) {res}return res }...

【C++】STL数据结构最全函数详解2-向量vector
关于STL,我们之前浅浅提过:这里 另外对于栈,这里有更加详尽的介绍:CSTL常用数据结构1详解---栈(stack)-CSDN博客 这个系列将会更加深入地从函数原型开始用详细的例子解释用法 首先这一篇介绍的是一个非常…...

阿里云 Quick BI使用介绍
Quick BI使用介绍 文章目录 阿里云 Quick BI使用介绍1. 创建自己的quick bi服务器2. 新建数据源3. 上传文件和 使用4. 开始分析 -选仪表盘5. 提供的图表6. 一个图表的设置使用小结 阿里云 Quick BI使用介绍 Quick BI是一款全场景数据消费式的BI平台,秉承全场景消费…...
LLMs之SuperPrompt:SuperPrompt的简介、使用方法、案例应用之详细攻略
LLMs之SuperPrompt:SuperPrompt的简介、使用方法、案例应用之详细攻略 目录 SuperPrompt的简介 SuperPrompt的使用方法 1、prompt SuperPrompt的案例应用 SuperPrompt的简介 SuperPrompt项目是一个开源项目,旨在通过设计特定的提示词来帮助我们更好…...
Java中的Web服务开发:RESTful API的最佳实践
Java中的Web服务开发:RESTful API的最佳实践 大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿! 在现代Web应用开发中,RESTful API是构建可伸缩、易于维护的Web服务的关键。…...
Linux创建虚拟磁盘并分区格式化
快速创建一个虚拟磁盘 你可以通过以下步骤在Linux上虚拟一个磁盘,并将其挂载到 /mnt/ 目录下: 步骤 1: 创建一个虚拟磁盘文件 使用 dd 命令创建一个虚拟磁盘文件(例如大小为1GB): dd if/dev/zero of/root/virtual_…...

面试经典150题——最后一个单词的长度
目录 题目链接:58. 最后一个单词的长度 - 力扣(LeetCode) 题目描述 示例 提示: 解法一:反向遍历 Java写法: C写法: 解法二:逆天解法 思路 存在的问题 总结 题目链接&…...

【C++】入门基础(上)
Hi,好久不见! 目录 1、C入门小基础 1.1 祖师爷--Bjarne Stroustrup(本贾尼斯特劳斯特卢普) 1.2 C参考文献 1.3 书籍推荐 2、C的第一个程序 3、命名空间 3.1 namespace的价值 3.2 namespace的定义 3.3 命名空间的使…...

Mac中Twig模版安装与SSTI漏洞学习
感谢大佬的文章参考学习。 SSTI:https://www.cnblogs.com/bmjoker/p/13508538.html Homebrew:快速开始 - Homebrew 中文网 Homebrew安装 一键快捷安装:默认使用中科大的源 /bin/bash -c "$(curl -fsSL https://gitee.com/ineo6/homeb…...
【20.5 python中的FastAPI】
python中的FastAPI FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,基于 Python 3.6 的类型提示。它利用了 Python 3.7 的新特性,如类型提示(Type Hints),来自动生成 A…...

研1日记13
正态分布: toTenor:转数字变为0-1 加载模型: model youmodel() model.load("路径") 测试单个样本:...
Go语言错误处理详解
Go语言以其简洁、高效和并发能力著称。在实际开发中,错误处理是一个不可避免且至关重要的部分。本文将深入探讨Go语言中的错误处理机制,涵盖其原理、使用方法、最佳实践,并提供丰富的代码示例和中文注释。 一、错误处理的基本概念 在Go语言…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...