SpriteBatchNode继承Node,并实现了TextureProtocol接口,重写了Node的addChild()方法,visit()方法以及draw()方法。

addChild()方法限制其子元素只能是Sprite,

并且子元素与SpriteBatchNode必须使用同一个Texture2D对象。

visit()用于阻止元素向下遍历,将所有子元素的绘制工作交给自己处理。

draw()方法使用BatchNodeCommand将绘制命令发送到RenderQueue,从而对所有子元素进行批绘制。

SpriteBatchNode使用TextureAtlas存储所有子精灵的顶点信息。TextureAtlas包含一个V3F_C4B_T2F_Quad数组和一个Texture2D对象,提供对quads数组的添加,删除,修改,排序等功能。这样,SpriteBatchNode所做的主要事情就是将与子元素相关的顶点信息存储到TextureAtlas中。最后,TextureAtlas提供了绘制quads的方法,

BatchCommand就是通过drawQuads()方法绘制的

代码流程:

1 调用

 auto spBatchNode = SpriteBatchNode::create("bbb.png");
spBatchNode->setPosition(Point::ZERO);
scene->addChild(spBatchNode);
spBatchNode->setPosition(,);
// spBatchNode->setScale(0.1); auto sp = Sprite::createWithTexture(spBatchNode->getTexture());
sp->setPosition(,);
spBatchNode->addChild(sp,); sp = Sprite::createWithTexture(spBatchNode->getTexture());
sp->setPosition(,);
spBatchNode->addChild(sp,-);

2 看一下SpriteBatchNode内部方法

初始化方法

bool SpriteBatchNode::initWithTexture(Texture2D *tex, ssize_t capacity)
{
CCASSERT(capacity>=, "Capacity must be >= 0"); _blendFunc = BlendFunc::ALPHA_PREMULTIPLIED; if(tex->hasPremultipliedAlpha())//alpha预乘,忽略
{
_blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
} //使用textureAtlas存储所有子精灵的顶点信息,通过对quads数组的添加,删除,修改排序等
_textureAtlas = new TextureAtlas(); if (capacity == )
{
capacity = DEFAULT_CAPACITY;
} _textureAtlas->initWithTexture(tex, capacity); updateBlendFunc(); _children.reserve(capacity);
//分配空间大小
_descendants.reserve(capacity);
//定义属于自己的着色器
setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR));
return true;
}

添加子元素方法:

void SpriteBatchNode::addChild(Node * child, int zOrder, const std::string &name)
{
CCASSERT(child != nullptr, "child should not be null");
CCASSERT(dynamic_cast<Sprite*>(child) != nullptr, "CCSpriteBatchNode only supports Sprites as children");
Sprite *sprite = static_cast<Sprite*>(child);
// check Sprite is using the same texture id
CCASSERT(sprite->getTexture()->getName() == _textureAtlas->getTexture()->getName(), "CCSprite is not using the same texture id"); Node::addChild(child, zOrder, name); appendChild(sprite);
}
// addChild helper, faster than insertChild
void SpriteBatchNode::appendChild(Sprite* sprite)
{
_reorderChildDirty=true;
sprite->setBatchNode(this);
sprite->setDirty(true); if(_textureAtlas->getTotalQuads() == _textureAtlas->getCapacity()) {
increaseAtlasCapacity();//重新调整容器大小,
} _descendants.push_back(sprite);//所有的子节点。
int index = static_cast<int>(_descendants.size()-); sprite->setAtlasIndex(index);//设置sprite的渲染节点为TextureAtlas的第index个数据 V3F_C4B_T2F_Quad quad = sprite->getQuad();
//把新sprite的顶点信息放到textureAtlas,供绘制使用,此时的顶点坐标还是在父亲中的坐标,不是世界坐标
_textureAtlas->insertQuad(&quad, index);//将sprite的顶点数据传入到TextureAtlas的index位置。 // add children recursively
auto& children = sprite->getChildren();
for(const auto &child: children) { //递归调用
appendChild(static_cast<Sprite*>(child)); //将sprite的所有数据加入到descendants。
}
}

遍历方法visit

// override visit
// don't call visit on it's children
void SpriteBatchNode::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
CC_PROFILER_START_CATEGORY(kProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit"); // CAREFUL:
// This visit is almost identical to CocosNode#visit
// with the exception that it doesn't call visit on it's children
//
// The alternative is to have a void Sprite#visit, but
// although this is less maintainable, is faster
//
if (! _visible)
{
return;
}
//排序,写的挺不错
sortAllChildren();
//得到本地坐标转换世界坐标的矩阵
uint32_t flags = processParentFlags(parentTransform, parentFlags); // IMPORTANT:
// To ease the migration to v3.0, we still support the Mat4 stack,
// but it is deprecated and your code should not rely on it
Director* director = Director::getInstance();
director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); draw(renderer, _modelViewTransform, flags); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
// FIX ME: Why need to set _orderOfArrival to 0??
// Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
// setOrderOfArrival(0); CC_PROFILER_STOP_CATEGORY(kProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit");
}
//override sortAllChildren
//排序功能
void SpriteBatchNode::sortAllChildren()
{
if (_reorderChildDirty)
{
std::sort(std::begin(_children), std::end(_children), nodeComparisonLess); //sorted now check all children
if (!_children.empty())
{
//first sort all children recursively based on zOrder
for(const auto &child: _children) {
child->sortAllChildren();
} ssize_t index=; //fast dispatch, give every child a new atlasIndex based on their relative zOrder (keep parent -> child relations intact)
// and at the same time reorder descendants and the quads to the right index
/*
//快速发送,根据每个孩子的相对zOrder给每个孩子一个新的atlasIndex(保持父母 - >孩子的关系不变)
       //同时重新排序descendants数组和四边形到正确的索引
*/
//此时的childern为根据localZ从小到大排序之后的数组
for(const auto &child: _children) {
Sprite* sp = static_cast<Sprite*>(child);
updateAtlasIndex(sp, &index);
}
} _reorderChildDirty=false;
}
}
//更新sprite和sprite子节点的atlas数据的index。
void SpriteBatchNode::updateAtlasIndex(Sprite* sprite, ssize_t* curIndex)
{
auto& array = sprite->getChildren();
auto count = array.size(); ssize_t oldIndex = ;
/*
在sortAllChildren,根据localZ对children进行了排序,从小到大排序,因为之前的AtlasIndex为顺序赋值的,所以要重新设置AtlasIndex
*/
//如果没有子节点,那么sprite就是curIndex位置了
if( count == )
{
oldIndex = sprite->getAtlasIndex();
sprite->setAtlasIndex(*curIndex);//重新递增赋值
sprite->setOrderOfArrival(); if (oldIndex != *curIndex){
//更新在_textureAtlas等中的位置
swap(oldIndex, *curIndex);
}
(*curIndex)++;
}
else
{
bool needNewIndex=true;//是否需要赋值新的索引
//先localZ<0的,然后sprite,最后localZ>0
//因为先对所有的子节点排序过。先localZ<0,然后localZ>0 //如果第一个localZ>=0那么先设置sprite的index
if (array.at()->getLocalZOrder() >= )
{
//sprite的第一个孩子local都大于0,剩下的孩子都大于0,所以孩子绘制都在自己上面
//all children are in front of the parent
oldIndex = sprite->getAtlasIndex();
sprite->setAtlasIndex(*curIndex);
sprite->setOrderOfArrival();
if (oldIndex != *curIndex)
{
swap(oldIndex, *curIndex);
}
(*curIndex)++; needNewIndex = false;//不需要新的索引,后面的按照顺序遍历就行
} for(const auto &child: array) {
Sprite* sp = static_cast<Sprite*>(child);
//找到了第一个localZorder>0的,设置sprite。
if (needNewIndex && sp->getLocalZOrder() >= )
{
oldIndex = sprite->getAtlasIndex();
sprite->setAtlasIndex(*curIndex);
sprite->setOrderOfArrival();
if (oldIndex != *curIndex) {
this->swap(oldIndex, *curIndex);
}
(*curIndex)++;
needNewIndex = false;
}
//递归调用
updateAtlasIndex(sp, curIndex);
} //这种情况是:所有的子节点都是localZ<0。
//all children have a zOrder < 0)
/*
sprite的children已经遍历完成,最后轮到sprite自己
*/
if (needNewIndex)
{
oldIndex = sprite->getAtlasIndex();
sprite->setAtlasIndex(*curIndex);
sprite->setOrderOfArrival();
if (oldIndex != *curIndex) {
swap(oldIndex, *curIndex);
}
(*curIndex)++;
}
}
}

swap把新排序的顶点数据更新到 quads中,用于为opengl提供基础数据

void SpriteBatchNode::swap(ssize_t oldIndex, ssize_t newIndex)
{
CCASSERT(oldIndex>= && oldIndex < (int)_descendants.size() && newIndex >= && newIndex < (int)_descendants.size(), "Invalid index"); V3F_C4B_T2F_Quad* quads = _textureAtlas->getQuads();
std::swap( quads[oldIndex], quads[newIndex] ); //update the index of other swapped item auto oldIt = std::next( _descendants.begin(), oldIndex );
auto newIt = std::next( _descendants.begin(), newIndex ); (*newIt)->setAtlasIndex(oldIndex);
// (*oldIt)->setAtlasIndex(newIndex); std::swap( *oldIt, *newIt );
}

重新的元素draw方法,并不是真的绘制,而是把绘制命令放到了_batchCommand中

void SpriteBatchNode::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
// Optimization: Fast Dispatch
if( _textureAtlas->getTotalQuads() == )
{
return;
} for(const auto &child: _children)
{ //更新孩子的坐标,递归
child->updateTransform();
} _batchCommand.init(
_globalZOrder,
getGLProgram(),
_blendFunc,
_textureAtlas,
transform);
renderer->addCommand(&_batchCommand);
}
void Sprite::updateTransform(void)
{
CCASSERT(_batchNode, "updateTransform is only valid when Sprite is being rendered using an SpriteBatchNode"); // recalculate matrix only if it is dirty
if( isDirty() ) { // If it is not visible, or one of its ancestors is not visible, then do nothing:
if( !_visible || ( _parent && _parent != _batchNode && static_cast<Sprite*>(_parent)->_shouldBeHidden) )
{
_quad.br.vertices = _quad.tl.vertices = _quad.tr.vertices = _quad.bl.vertices = Vec3(,,);
_shouldBeHidden = true;
}
else
{
_shouldBeHidden = false;
//如果直接事batchNode的第一子元素,那么直接getNodeToParentTransform
if( ! _parent || _parent == _batchNode )
{
//得到子坐标转换父亲坐标的矩阵
_transformToBatch = getNodeToParentTransform();
}
else //如果是batchNode的孙子或者往后,那么要下面这么做,孙子乘以transformToBatch得到在batch中的坐标
{
CCASSERT( dynamic_cast<Sprite*>(_parent), "Logic error in Sprite. Parent must be a Sprite");
const Mat4 &nodeToParent = getNodeToParentTransform(); Mat4 &parentTransform = static_cast<Sprite*>(_parent)->_transformToBatch;
_transformToBatch = parentTransform * nodeToParent;
} //
// calculate the Quad based on the Affine Matrix
// Size &size = _rect.size; float x1 = _offsetPosition.x;
float y1 = _offsetPosition.y; float x2 = x1 + size.width;
float y2 = y1 + size.height; /*
_transformToBatch 为 本地坐标转父亲坐标的旋转矩阵。这个矩阵乘以sprite的
[x,y,z,1],得到sprite在Batch中的的坐标 */
float x = _transformToBatch.m[];
float y = _transformToBatch.m[]; float cr = _transformToBatch.m[];
float sr = _transformToBatch.m[];
float cr2 = _transformToBatch.m[];
float sr2 = -_transformToBatch.m[]; float ax = x1 * cr - y1 * sr2 + x;
float ay = x1 * sr + y1 * cr2 + y; float bx = x2 * cr - y1 * sr2 + x;
float by = x2 * sr + y1 * cr2 + y; float cx = x2 * cr - y2 * sr2 + x;
float cy = x2 * sr + y2 * cr2 + y; float dx = x1 * cr - y2 * sr2 + x;
float dy = x1 * sr + y2 * cr2 + y; _quad.bl.vertices = Vec3( RENDER_IN_SUBPIXEL(ax), RENDER_IN_SUBPIXEL(ay), _positionZ );
_quad.br.vertices = Vec3( RENDER_IN_SUBPIXEL(bx), RENDER_IN_SUBPIXEL(by), _positionZ );
_quad.tl.vertices = Vec3( RENDER_IN_SUBPIXEL(dx), RENDER_IN_SUBPIXEL(dy), _positionZ );
_quad.tr.vertices = Vec3( RENDER_IN_SUBPIXEL(cx), RENDER_IN_SUBPIXEL(cy), _positionZ );
} // MARMALADE CHANGE: ADDED CHECK FOR nullptr, TO PERMIT SPRITES WITH NO BATCH NODE / TEXTURE ATLAS
//把更新的四边形保存到 内存中,此时四边形的四个顶点坐标为在Batch中的坐标,不是世界坐标
if (_textureAtlas)
{
_textureAtlas->updateQuad(&_quad, _atlasIndex);
} _recursiveDirty = false;
setDirty(false);
} // MARMALADE CHANGED
// recursively iterate over children
/* if( _hasChildren )
{
// MARMALADE: CHANGED TO USE Node*
// NOTE THAT WE HAVE ALSO DEFINED virtual Node::updateTransform()
arrayMakeObjectsPerformSelector(_children, updateTransform, Sprite*);
}*/
//递归更新
Node::updateTransform();
}

3 render中执行command

void Renderer::visitRenderQueue(const RenderQueue& queue)
{
ssize_t size = queue.size(); for (ssize_t index = ; index < size; ++index)
{
auto command = queue[index];
auto commandType = command->getType();
if(RenderCommand::Type::BATCH_COMMAND == commandType)
{
flush();
auto cmd = static_cast<BatchCommand*>(command);
cmd->execute();
}
else
{
CCLOGERROR("Unknown commands in renderQueue");
}
}
}

4  RenderCommand 内执行execute

void BatchCommand::execute()
{
// Set material
_shader->use(); _shader->setUniformsForBuiltins(_mv);//_mv为模型矩阵,本地坐标乘以mv,得到世界坐标 glActiveTexture(GL_TEXTURE0 + );//0号纹理单元
//将一个纹理对象设置为当前纹理对象,同时她会指派给当前激活了的纹理单元
GL::bindTexture2D(_textureID);
GL::blendFunc(_blendType.src, _blendType.dst);//颜色混合 if ( _textureAtlas->getTexture()->getHasAlphaTexture()) {
auto loc =glGetUniformLocation(_shader->getProgram(), "CC_Texture1");
glUniform1i(loc,);
auto alpha=Director::getInstance()->getTextureCache()->addImage(_textureAtlas->getTexture()->getAlphaTexture());
glActiveTexture(GL_TEXTURE0 + );
glBindTexture(GL_TEXTURE_2D, alpha->getName());
} auto locHasAlpha=glGetUniformLocation(_shader->getProgram(),"u_hasAlpha");
glUniform1f(locHasAlpha, _textureAtlas->getTexture()->getHasAlphaTexture()?1.0f:0.0f); // Draw,开始opengl画
_textureAtlas->drawQuads();
}

5 textureAtlas执行drawQuads方法

void TextureAtlas::drawQuads()
{
this->drawNumberOfQuads(_totalQuads, );
} void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads)
{
CCASSERT(numberOfQuads>=, "numberOfQuads must be >= 0");
this->drawNumberOfQuads(numberOfQuads, );
}
//渲染start位置的numberOfQuads的数据
void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads, ssize_t start)
{
CCASSERT(numberOfQuads>= && start>=, "numberOfQuads and start must be >= 0"); if(!numberOfQuads)
return; GL::bindTexture2D(_texture->getName()); if (Configuration::getInstance()->supportsShareableVAO())
{
//
// Using VBO and VAO
// // XXX: update is done in draw... perhaps it should be done in a timer
if (_dirty)
{
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[]);
// option 1: subdata
// glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[0])*start, sizeof(_quads[0]) * n , &_quads[start] ); // option 2: data
// glBufferData(GL_ARRAY_BUFFER, sizeof(quads_[0]) * (n-start), &quads_[start], GL_DYNAMIC_DRAW); // option 3: orphaning + glMapBuffer /****************************
_quads中的顶点坐标为所有sprite在batch中的坐标,再顶点着色器中在转换为世界坐标
****************************/
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[]) * (numberOfQuads-start), nullptr, GL_DYNAMIC_DRAW);
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(buf, _quads, sizeof(_quads[])* (numberOfQuads-start));
glUnmapBuffer(GL_ARRAY_BUFFER); glBindBuffer(GL_ARRAY_BUFFER, ); _dirty = false;
} GL::bindVAO(_VAOname); #if CC_REBIND_INDICES_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[]);
#endif glDrawElements(GL_TRIANGLES, (GLsizei) numberOfQuads*, GL_UNSIGNED_SHORT, (GLvoid*) (start**sizeof(_indices[])) ); #if CC_REBIND_INDICES_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, );
#endif // glBindVertexArray(0);
}
else
{
//
// Using VBO without VAO
// #define kQuadSize sizeof(_quads[0].bl)
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[]); // XXX: update is done in draw... perhaps it should be done in a timer
if (_dirty)
{
glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[])*start, sizeof(_quads[]) * numberOfQuads , &_quads[start] );
_dirty = false;
} GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX); // vertices
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, , GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices)); // colors
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, , GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors)); // tex coords
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, , GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords)); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[]); glDrawElements(GL_TRIANGLES, (GLsizei)numberOfQuads*, GL_UNSIGNED_SHORT, (GLvoid*) (start**sizeof(_indices[]))); glBindBuffer(GL_ARRAY_BUFFER, );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, );
} CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(,numberOfQuads*); CHECK_GL_ERROR_DEBUG();
}

以上主要步骤完成了SpriteBatchNode中sprite的绘制

cocos源码分析--SpriteBatchNode绘图原理的更多相关文章

  1. cocos源码分析--ClippingNode绘图原理

    在OpenGL 绘制过程中,与帧缓冲有关的是模版,深度测试,混合操作.模版测试使应用程序可以定义一个遮罩,在遮罩内的片段将被保留或者丢弃,在遮罩外的片段操作行为则相反.深度测试用来剔除那些被场景遮挡的 ...

  2. cocos源码分析--Sprite绘图原理

    精灵是2D游戏中最重要的元素,可以用来构成游戏中的元素,如人物,建筑等,用Sprite类表示,他将一张纹理的一部分或者全部矩形区域绘制到屏幕上.我们可以使用精灵表来减少OpenGL ES 绘制的次数, ...

  3. Guava 源码分析(Cache 原理 对象引用、事件回调)

    前言 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理. 文末提到了回收机制.移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析. 在开 ...

  4. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  5. 【转】MaBatis学习---源码分析MyBatis缓存原理

    [原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...

  6. php中foreach源码分析(编译原理)

    php中foreach源码分析(编译原理) 一.总结 编译原理(lex and yacc)的知识 二.php中foreach源码分析 foreach是PHP中很常用的一个用作数组循环的控制语句.因为它 ...

  7. Kafka源码分析及图解原理之Producer端

    一.前言 任何消息队列都是万变不离其宗都是3部分,消息生产者(Producer).消息消费者(Consumer)和服务载体(在Kafka中用Broker指代).那么本篇主要讲解Producer端,会有 ...

  8. Robotium源码分析之运行原理

    从上一章<Robotium源码分析之Instrumentation进阶>中我们了解到了Robotium所基于的Instrumentation的一些进阶基础,比如它注入事件的原理等,但Rob ...

  9. Dubbo 源码分析 - 自适应拓展原理

    1.原理 我在上一篇文章中分析了 Dubbo 的 SPI 机制,Dubbo SPI 是 Dubbo 框架的核心.Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol.Clu ...

随机推荐

  1. CLR(Common Language Runtime) 公共语言运行库

    .NET Core 使用 CoreCLR .NET Framework 使用CLR. 1. 将代码编译为IL (Intermediate Language) 2. CLR 把IL 编译为平台专用的本地 ...

  2. ReportViewer的使用总结

    1.换行符:chr(13)&chr(10) 2.时间字符串格式化:  =IIF(Trim(Fields!business_time.Value).Length=6,   Left(Trim(F ...

  3. H5公共样式,用于所有H5开发页面

    @charset "UTF-8"; /* H5公共样式,用于所有H5开发页面*/ html { font-family: "Microsoft Yahei", ...

  4. create a simple COM object

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAArsAAAGYCAIAAADN0b3QAAAgAElEQVR4nO29749c1b2nW/4Lzh8wUr

  5. webpack 提取 manifest 文件

    当 webpack 生成 bundle 时, 它同时维护一个 manifest 文件.你可以在生成的 vendor bundle 中找到它.manifest 文件描述了哪些文件需要 webpack 加 ...

  6. js 的深拷贝

    出处:https://www.cnblogs.com/Chen-XiaoJun/p/6217373.html function deepClone(initalObj, finalObj) { var ...

  7. laravel 一表關聯二表,二表關聯三表,通過一表controller拿三表數據

    model 一表關聯二表 public function ordercode() { return $this->hasOne(\App\Models\OrderCode::class,'id' ...

  8. jquery遍历table的tr获取td的值

    方法一: var siginArray = []; $("#tbody").children("tr").each(function () { var sigi ...

  9. C# 虚方法、override和new

    使用new关键字来做调用 using System; using System.Collections.Generic; using System.Linq; using System.Text; u ...

  10. 单进程与 多进程关系及区别(多进程系统linux)

    单进程编程:顺序执行 数据同步 复杂度低 用途单一 多进程编程:同时执行 数据异步 复杂度高 用途广泛 1. 多进程的优势在于任务的独立性,比如某个任务单独作为一个进程的话,崩溃只影响自己的服务,其他 ...