一、概括
该类继承了ParalleTask,而ParalleTask又继承了subtask,依赖Serieswork(WFGOTask文中有讲),该类主要实现串行任务的并行工作。
PS:在WFGOTask文中,对于Serieswork的工作有一个错误,在执行任务的最后还会判断last是否存在以及执行callback,所以串行任务流该为下图
该类下有个数组为subtasks,用来存储一条条的串行任务,假设现在有四条并行任务,那么该类的工作模式可以理解为
PS:ParallelWork同样也有回调函数(callback)
同时,我们知道ParallelTask继承了subtask,那么ParallelTask也是一种任务。如果在串行任务流中存在ParallelTask,假设图中的last为ParallelTask,就为
以此类推,可以一直衍生下去
二、类图
三、ParallelTask
1、成员变量和成员函数
subtasks为指针数组,每个指针用来存串行任务的第一个任务
class ParallelTask : public SubTask
{
public:
ParallelTask(SubTask **subtasks, size_t n)
{
this->subtasks = subtasks;
this->subtasks_nr = n;
}
SubTask **get_subtasks(size_t *n) const
{
*n = this->subtasks_nr;
return this->subtasks;
}
void set_subtasks(SubTask **subtasks, size_t n)
{
this->subtasks = subtasks;
this->subtasks_nr = n;
}
public:
virtual void dispatch();
protected:
SubTask **subtasks;//指针数组,用来存取串行任务
size_t subtasks_nr;//串行任务的条数
private:
size_t nleft;//串行任务的条数
friend class SubTask;
};
2、void ParallelTask::dispatch()
在这一层中多态出来了subtask中的dispatch
功能:开启subtasks中的每一条串行任务
其中每次开启任务的时候都会设置subtask中的两个指针,parent,entry,具体用途在subtask_done函数中体现
parent指向当前的的Parallel Task、entry指向当前人物流的第一个任务
void ParallelTask::dispatch()
{
SubTask **end = this->subtasks + this->subtasks_nr;
SubTask **p = this->subtasks;
this->nleft = this->subtasks_nr;
if (this->nleft != 0)
{
do
{
(*p)->parent = this;//指向当前的ParallelTask
(*p)->entry = p;//指向当前任务流的第一个任务
(*p)->dispatch();//此时执行的dispatch是任务流中多态出来的另一个
} while (++p != end);
}
else
this->subtask_done();
}
3、subtask_done()
在WFGOTask中我们知道,除掉一些细节的操作,一个任务的大致过程可以看成如下图
又由WFGOTask中,可以得出,无论哪个类派生出的done,它最后都会返回下一个任务
void SubTask::subtask_done()
{
SubTask *cur = this;
ParallelTask *parent;
SubTask **entry;
while (1)
{
parent = cur->parent;
entry = cur->entry;
cur = cur->done();//返回下一个任务
//情况1:如果存在下一个任务
if (cur)
{
cur->parent = parent;//设置下一个任务的父节点,即指向当前的ParallelTask
cur->entry = entry;//设置指向当前的串行任务的指针
//判断当前任务是否在并行任务流中,如果存在则更新当前串行任务流中的第一个任务
if (parent)
*entry = cur;
cur->dispatch();//执行下一个任务的行为
}
//情况2:一条串行任务运行完成
else if (parent)
{
if (__sync_sub_and_fetch(&parent->nleft, 1) == 0)
{
cur = parent;
continue;
}
}
break;
}
}
3.1、情况一:存在下一个任务
在该情况下,会先设置新任务节点的指针指向,可以理解成下图
3.2、情况二:一条串行任务流的结束
在情况二,会先判断parent指针是否有内容,即判断当前任务流是否在并行任务中。如果处在并行任务中,则对nleft减一。当为零时,则表示并行任务全部结束,于是耿欣cur的指向为parent,并且指向done,而此时执行的done为ParalleWork派生出来的done。
这里有个细节,SeriesWork中的pop,在执行完回调函数后会判断是否处在并行队列中,如果在,删除该串行流的操作由ParallelWork负责
SubTask *SeriesWork::pop_task()
{
SubTask *task;
this->mutex.lock();
if (this->front != this->back)
{
task = this->queue[this->front];
if (++this->front == this->queue_size)
this->front = 0;
}
else
{
task = this->last;
this->last = NULL;
}
this->mutex.unlock();
if (!task)
{
this->finished = true;
if (this->callback)
this->callback(this);
if (!this->in_parallel)
delete this;
}
return task;
}
4、串行任务的细节
我们知道done是由派生类衍生出来的多态类,而衍生出来的任务,有的存在回调函数,有的不存在回调函数。后者可以按照上文中的图来理解,而前者的运行流程为下图
四、ParallelWork
1、成员函数和成员函数
class ParallelWork : public ParallelTask
{
public:
void start()
{
assert(!series_of(this));
Workflow::start_series_work(this, nullptr);
}
void dismiss()
{
assert(!series_of(this));
delete this;
}
public:
void add_series(SeriesWork *series);//添加一条串行任务
public:
void *get_context() const { return this->context; }
void set_context(void *context) { this->context = context; }
public:
SeriesWork *series_at(size_t index)
{
if (index < this->subtasks_nr)
return this->all_series[index];
else
return NULL;
}
const SeriesWork *series_at(size_t index) const
{
if (index < this->subtasks_nr)
return this->all_series[index];
else
return NULL;
}
SeriesWork& operator[] (size_t index)
{
return *this->series_at(index);
}
const SeriesWork& operator[] (size_t index) const
{
return *this->series_at(index);
}
size_t size() const { return this->subtasks_nr; }
public:
void set_callback(parallel_callback_t callback)
{
this->callback = std::move(callback);
}
protected:
virtual SubTask *done();
protected:
void *context;
parallel_callback_t callback;
private:
void expand_buf();
private:
size_t buf_size;
SeriesWork **all_series;
protected:
ParallelWork(parallel_callback_t&& callback);
ParallelWork(SeriesWork *const all_series[], size_t n,
parallel_callback_t&& callback);
virtual ~ParallelWork();
friend class Workflow;
};
2、构造函数
//设置回调函数,并且new出一个数组给基类的Parallel
ParallelWork::ParallelWork(parallel_callback_t&& cb) :
ParallelTask(new SubTask *[2 * 4], 0),
callback(std::move(cb))
{
this->buf_size = 4;
this->all_series = (SeriesWork **)&this->subtasks[this->buf_size];
this->context = NULL;
}
//将穿入的 all_series赋给内部的串行任务数组
ParallelWork::ParallelWork(SeriesWork *const all_series[], size_t n,
parallel_callback_t&& cb) :
ParallelTask(new SubTask *[2 * (n > 4 ? n : 4)], n),
callback(std::move(cb))
{
size_t i;
this->buf_size = (n > 4 ? n : 4);
this->all_series = (SeriesWork **)&this->subtasks[this->buf_size];
for (i = 0; i < n; i++)
{
assert(!all_series[i]->in_parallel);
all_series[i]->in_parallel = true;
this->all_series[i] = all_series[i];
this->subtasks[i] = all_series[i]->first;
}
this->context = NULL;
}
在构造函数中,我们可以发现在给SubTask的空间为2n,那多出来的n个空间用来存什么呢?
通过变量名可以知道all_series是个指针数组,里面的指针存取serieswork的指针
而all_series用的空间是subtasks多出来的n个空间,所以逻辑上可以吧任务数组的每个位置理解为
3、expand_buf()
每次扩容涨两倍
void ParallelWork::expand_buf()
{
SubTask **buf;
size_t size;
this->buf_size *= 2;
buf = new SubTask *[2 * this->buf_size];
size = this->subtasks_nr * sizeof (void *);
memcpy(buf, this->subtasks, size);
memcpy(buf + this->buf_size, this->all_series, size);
delete []this->subtasks;
this->subtasks = buf;
this->all_series = (SeriesWork **)&buf[this->buf_size];
}
4、add_series(SeriesWork *series)
void ParallelWork::add_series(SeriesWork *series)
{
if (this->subtasks_nr == this->buf_size)
this->expand_buf();
assert(!series->in_parallel);
series->in_parallel = true;
this->all_series[this->subtasks_nr] = series;
this->subtasks[this->subtasks_nr] = series->first;
this->subtasks_nr++;
}
5、ParallelWork::done()
SubTask *ParallelWork::done()
{
SeriesWork *series = series_of(this);
size_t i;
if (this->callback)
this->callback(this);
for (i = 0; i < this->subtasks_nr; i++)
delete this->all_series[i];
this->subtasks_nr = 0;
delete this;
return series->pop();
}
在该称给出subtask的done的具体行为,完成三个任务
1、执行ParallelWork的回调函数
2、释放所有串行任务的空间
3、返回下一个任务
前文说到,ParallelWork也是一种任务,所以它也会返回下一个任务
6、析构函数
ParallelWork::~ParallelWork()
{
size_t i;
for (i = 0; i < this->subtasks_nr; i++)
{
this->all_series[i]->in_parallel = false;
this->all_series[i]->dismiss_recursive();
}
delete []this->subtasks;
}