mesytec-mnode/external/taskflow-3.8.0/3rd-party/ff/gt.hpp
2025-01-04 01:25:05 +01:00

937 lines
29 KiB
C++

/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*!
* \file gt.hpp
* \ingroup building_blocks
*
* \brief Farm Collector (it is not a ff_node)
*
* It Contains the \p ff_gatherer class and methods which are used to model the \a
* Collector node, which is optionally used to gather tasks coming from
* workers.
*
* \todo Documentation to be rewritten. To be substituted with ff_minode?
*/
/* ***************************************************************************
*
* FastFlow is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3 as
* published by the Free Software Foundation.
* Starting from version 3.0.1 FastFlow is dual licensed under the GNU LGPLv3
* or MIT License (https://github.com/ParaGroup/WindFlow/blob/vers3.x/LICENSE.MIT)
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*
****************************************************************************
*/
#ifndef FF_GT_HPP
#define FF_GT_HPP
#include <iosfwd>
#include <ff/svector.hpp>
#include <ff/utils.hpp>
#include <ff/node.hpp>
namespace ff {
/*!
* \class ff_gatherer
* \ingroup building_blocks
*
* \brief A class representing the \a Collector node in a \a Farm skeleton.
*
* This class models the \p gatherer, which wraps all the methods and
* structures used by the \a Collector node in a \p Farm skeleton. The \p farm
* can be seen as a three-stages \p pipeline, the stages being a \p
* ff_loadbalancer called \a emitter, a pool of \p ff_node called \a workers
* and - optionally - a \p ff_gatherer called \a collector. The \a Collector
* node can be used to gather the results coming from the computations
* executed by the pool of \a workers. The \a collector can also be
* connected to the \a emitter node via a feedback channel, in order to create
* a \p farm-with-feedback skeleton.
*
* This class is defined in \ref gt.hpp
*
*/
class ff_gatherer: public ff_thread {
friend class ff_farm;
friend class ff_pipeline;
friend class ff_minode;
public:
enum {TICKS2WAIT=5000};
protected:
inline bool init_input_blocking(pthread_mutex_t *&m,
pthread_cond_t *&c,
bool /*feedback*/=true) {
if (cons_m == nullptr) {
assert(cons_c==nullptr);
cons_m = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
cons_c = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
assert(cons_m); assert(cons_c);
if (pthread_mutex_init(cons_m, NULL) != 0) return false;
if (pthread_cond_init(cons_c, NULL) != 0) return false;
}
m = cons_m, c = cons_c;
return true;
}
// producer
inline bool init_output_blocking(pthread_mutex_t *&m,
pthread_cond_t *&c,
bool feedback=true) {
if (filter &&
( (filter->get_out_buffer()!=nullptr) || filter->isMultiOutput() ) ) { // (*)
return filter->init_output_blocking(m, c, feedback);
}
if (prod_m == nullptr) {
assert(prod_c == nullptr);
prod_m = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
prod_c = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
assert(prod_m); assert(prod_c);
if (pthread_mutex_init(prod_m, NULL) != 0) return false;
if (pthread_cond_init(prod_c, NULL) != 0) return false;
}
m = prod_m, c = prod_c;
return true;
}
inline void set_output_blocking(pthread_mutex_t *&m,
pthread_cond_t *&c,
bool canoverwrite=false) {
p_cons_m = m, p_cons_c = c;
if (filter && (filter->get_out_buffer()!=nullptr)) { // CHECK: asimmetry with test here (*)
if (filter->isMultiInput() && !filter->isComp()) return; // to avoid recursive calls to set_output_blocking
filter->set_output_blocking(m,c, canoverwrite);
}
}
inline pthread_cond_t &get_cons_c() { return *cons_c; }
/**
* \brief Selects a worker.
*
* It gets the next worker using the Round Robin policy. The selected
* worker has to be alive (and kicking).
*
* \return The next worker to be selected.
*
*/
virtual inline ssize_t selectworker() {
do
nextr = (nextr+1) % running;
while(offline[nextr]);
return nextr;
}
/**
* \brief Notifies the EOS
*
* It is a virtual function and is used to notify EOS
*/
virtual inline void notifyeos(int /*id*/) {}
/**
* \brief Gets the number of attempts.
*
* The number of attempts before wasting some times and than retry
*/
virtual inline size_t nattempts() { return getnworkers();}
/**
* \brief Loses the time out.
*
* It is a virutal function which defines the number of ticks to be waited.
*
*/
virtual inline void losetime_out(unsigned long ticks=TICKS2WAIT) {
FFTRACE(lostpushticks+=ticks;++pushwait);
#if defined(SPIN_USE_PAUSE)
const long n = (long)ticks/2000;
for(int i=0;i<=n;++i) PAUSE();
#else
ticks_wait(ticks);
#endif /* SPIN_USE_PAUSE */
}
/**
* \brief Loses the time in
*
* It is a virutal function which defines the number of ticks to be waited.
*
*/
virtual inline void losetime_in(unsigned long ticks=TICKS2WAIT) {
FFTRACE(lostpopticks+=ticks;++popwait);
#if defined(SPIN_USE_PAUSE)
const long n = (long)ticks/2000;
for(int i=0;i<=n;++i) PAUSE();
#else
ticks_wait(ticks);
#endif /* SPIN_USE_PAUSE */
}
/**
* \brief It gathers the tasks.
*
* It keeps selecting the worker. If a worker has task, then the worker is
* returned. Otherwise a tick is wasted and then keep looking for the
* worker with the task.
*
* \return It returns the workers with a taks if successful. Otherwise -1
* is returned.
*/
virtual ssize_t gather_task(void ** task) {
unsigned int cnt;
do {
cnt=0;
do {
nextr = selectworker();
//assert(offline[nextr]==false);
if (workers[nextr]->get(task)) {
return nextr;
}
else if (++cnt == nattempts()) break;
} while(1);
if (blocking_in) {
struct timespec tv;
timedwait_timeout(tv);
pthread_mutex_lock(cons_m);
pthread_cond_timedwait(cons_c, cons_m, &tv);
pthread_mutex_unlock(cons_m);
} else losetime_in();
} while(1);
return -1;
}
/**
* \brief Pushes the task in the tasks queue.
*
* It pushes the tasks in a queue.
*/
inline bool push(void * task, unsigned long retry=((unsigned long)-1), unsigned long ticks=(TICKS2WAIT)) {
if (blocking_out) {
if (!filter) {
bool empty=buffer->empty();
while(!buffer->push(task)) {
empty = false;
struct timespec tv;
timedwait_timeout(tv);
pthread_mutex_lock(prod_m);
pthread_cond_timedwait(prod_c,prod_m, &tv);
pthread_mutex_unlock(prod_m);
}
if (empty) pthread_cond_signal(p_cons_c);
} else {
bool empty=filter->get_out_buffer()->empty();
while(!filter->push(task)) {
empty=false;
struct timespec tv;
timedwait_timeout(tv);
pthread_mutex_lock(prod_m);
pthread_cond_timedwait(prod_c,prod_m,&tv);
pthread_mutex_unlock(prod_m);
}
if (empty) pthread_cond_signal(p_cons_c);
}
return true;
}
if (!filter) {
for(unsigned long i=0;i<retry;++i) {
if (buffer->push(task)) return true;
losetime_out(ticks);
}
} else
for(unsigned long i=0;i<retry;++i) {
if (filter->push(task)) return true;
losetime_out();
}
return false;
}
/**
* \brief Pop a task out of the queue.
*
* It pops the task out of the queue.
*
* \return \p false if not successful, otherwise \p true is returned.
*
*/
bool pop(void ** task) {
if (!buffer) return false;
while (! buffer->pop(task)) {
losetime_in();
}
return true;
}
/**
* \brief Pop a task
*
* It pops the task.
*
* \return The task popped from the buffer.
*/
bool pop_nb(void ** task) {
if (!buffer) return false;
return buffer->pop(task);
}
void set_input_channelid(ssize_t id, bool fromin=true) { channelid = id; frominput=fromin;}
static bool ff_send_out_collector(void * task, int id,
unsigned long retry,
unsigned long ticks, void *obj) {
(void)id;
bool r = ((ff_gatherer *)obj)->push(task, retry, ticks);
#if defined(FF_TASK_CALLBACK)
if (r) ((ff_gatherer *)obj)->callbackOut(obj);
#endif
return r;
}
bool fromInput() const { return frominput; }
#if defined(FF_TASK_CALLBACK)
void callbackIn(void *t=NULL) { filter->callbackIn(t); }
void callbackOut(void *t=NULL) { filter->callbackOut(t); }
#endif
public:
/**
* \brief Constructor
*
* It creates \p max_num_workers and \p NULL pointers to worker objects.
*/
ff_gatherer(int max_num_workers):
max_nworkers(max_num_workers), running(-1), nextr(-1),
neos(0),neosnofreeze(0),channelid(-1),feedbackid(0),
filter(NULL), workers(max_nworkers), offline(max_nworkers), buffer(NULL),
skip1pop(false),frominput(false) {
time_setzero(tstart);time_setzero(tstop);
time_setzero(wtstart);time_setzero(wtstop);
wttime=0;
p_cons_m = NULL, p_cons_c = NULL;
offline.resize(max_nworkers);
blocking_in = blocking_out = FF_RUNTIME_MODE;
FFTRACE(taskcnt=0;lostpushticks=0;pushwait=0;lostpopticks=0;popwait=0;ticksmin=(ticks)-1;ticksmax=0;tickstot=0);
}
ff_gatherer& operator=(ff_gatherer&& gtin) {
set_barrier(gtin.get_barrier());
cons_m = gtin.cons_m;
cons_c = gtin.cons_c;
prod_m = gtin.prod_m;
prod_c = gtin.prod_c;
p_cons_m = gtin.p_cons_m;
p_cons_c = gtin.p_cons_c;
buffer = gtin.buffer;
blocking_in = gtin.blocking_in;
blocking_out = gtin.blocking_out;
skip1pop = gtin.skip1pop;
frominput = gtin.frominput;
filter = gtin.filter;
workers = gtin.workers;
gtin.cons_m = nullptr;
gtin.cons_c = nullptr;
gtin.prod_m = nullptr;
gtin.prod_c = nullptr;
gtin.set_barrier(nullptr);
return *this;
}
virtual ~ff_gatherer() {
if (cons_m) {
pthread_mutex_destroy(cons_m);
free(cons_m);
cons_m = nullptr;
}
if (cons_c) {
pthread_cond_destroy(cons_c);
free(cons_c);
cons_c = nullptr;
}
if (prod_m) {
pthread_mutex_destroy(prod_m);
free(prod_m);
prod_m = nullptr;
}
if (prod_c) {
pthread_cond_destroy(prod_c);
free(prod_c);
prod_c = nullptr;
}
}
/**
* \brief Sets the filer
*
* It sents the \p ff_node to the filter.
*
* \return 0 if successful, otherwise a negative value is returned.
*/
int set_filter(ff_node * f) {
if (filter) {
error("GT, setting collector filter\n");
return -1;
}
filter = f;
// NOTE: if set_output_blocking has been already executed (for example by the pipeline),
// then we have to set the blocking stuff also for the filter
if ( ((filter->get_out_buffer() != nullptr) || filter->isMultiOutput()) &&
p_cons_m!=nullptr) {
filter->set_output_blocking(p_cons_m, p_cons_c);
}
return 0;
}
// Sets where feedback channels end (if any)
// All channels were registered in input, first feedback ones then the input ones.
void set_feedbackid_threshold(size_t id) { feedbackid = id; }
ff_node *get_filter() const { return (filter==(ff_node*)this)?NULL:filter; }
void reset_filter() {
if (filter == NULL) return;
filter->registerCallback(NULL,NULL);
filter->setThread(NULL);
filter = NULL;
}
/**
* \brief Sets output buffer
*
* It sets the output buffer.
*/
int set_output_buffer(FFBUFFER * const buff) {
if (filter) {
if (filter->set_output_buffer(buff)<0) return -1;
// NOTE: if set_output_blocking has been already executed (for example by the pipeline),
// then we have to set the blocking stuff also for the filter
if ( p_cons_m!=nullptr) {
filter->set_output_blocking(p_cons_m, p_cons_c);
}
}
if (buffer) return -1;
buffer=buff;
return 0;
}
/**
* \brief Gets the channel id
*
* It gets the \p channelid.
*
* \return The \p channelid is returned.
*/
ssize_t get_channel_id() const { return channelid;}
size_t get_num_inchannels() const { return (size_t)(running); }
size_t get_num_outchannels() const {
if (filter && (filter != (ff_node*)this))
return filter->get_num_outchannels();
return (buffer?1:0);
}
size_t get_num_feedbackchannels() const {
return feedbackid;
}
/**
* \brief Gets the number of worker threads currently running.
*
* It gets the number of threads currently running.
*
* \return Number of worker threads
*/
inline size_t getnworkers() const { return (size_t)(running-neos-neosnofreeze); }
inline size_t getrunning() const { return (size_t)running;}
/**
* \brief Get the number of workers
*
* It returns the number of total workers registered
*
* \return Number of worker
*/
inline size_t getNWorkers() const { return workers.size();}
const svector<ff_node*>& getWorkers() const { return workers; }
/**
* \brief Skips the first pop
*
* It determine whether the first pop should be skipped or not.
*
* \return Always \true is returned.
*/
void skipfirstpop(bool sk=true) { skip1pop=sk; }
#ifdef DFF_ENABLED
void skipallpop(bool sk = true) { _skipallpop = sk;}
#endif
/**
* \brief Gets the ouput buffer
*
* It gets the output buffer
*
* \return \p buffer is returned.
*/
FFBUFFER * get_out_buffer() const { return buffer;}
/**
* \brief Register the given worker to the list of workers.
*
* It registers the given worker to the list of workers.
*
* \return 0 if successful, or -1 if not successful.
*/
int register_worker(ff_node * w) {
if (workers.size()>=max_nworkers) {
error("GT, max number of workers reached (max=%ld)\n",max_nworkers);
return -1;
}
workers.push_back(w);
return 0;
}
/**
* \brief Initializes the gatherer task.
*
* It is a virtual function to initialise the gatherer task.
*
* \return It returns the task if successful, otherwise 0 is returned.
*/
virtual int svc_init() {
#if !defined(HAVE_PTHREAD_SETAFFINITY_NP) && !defined(NO_DEFAULT_MAPPING)
if (this->get_mapping()) {
int cpuId = filter?filter->getCPUId():-1;
if (ff_mapThreadToCpu((cpuId<0) ? (cpuId=threadMapper::instance()->getCoreId(tid)) : cpuId)!=0)
error("Cannot map thread %d to CPU %d, mask is %u, size is %u, going on...\n",tid, (cpuId<0) ? threadMapper::instance()->getCoreId(tid) : cpuId, threadMapper::instance()->getMask(), threadMapper::instance()->getCListSize());
if (filter) filter->setCPUId(cpuId);
}
#endif
gettimeofday(&tstart,NULL);
for(ssize_t i=0;i<running;++i) offline[i]=false;
if (filter) {
if (filter->isComp() && !filter->isMultiInput())
filter->set_neos(running);
return filter->svc_init();
}
return 0;
}
/**
* \brief The gatherer task
*
* It is a virtual function to be used as the gatherer task.
*
* \return It returns the task.
*/
virtual void * svc(void *) {
void * ret = FF_EOS;
void * task = NULL;
bool filter_outpresent = false;
bool outpresent = (buffer != NULL);
bool skipfirstpop = skip1pop;
// the following case is possible when the there is a filter that is a composition
if ( filter &&
( (filter->get_out_buffer()!=NULL) || filter->isMultiOutput() ) ) {
filter_outpresent=true;
//set_out_buffer(filter->get_in_buffer());
}
// it is possible that a standard node has been automatically (and transparently)
// transformed into a multi-input node (see mi_transformer).
// In this case we want to call notifyeos only when we have received EOS from all
// input channel.
bool notify_each_eos = filter ? (filter->neos==1): false;
// TODO: skipallpop missing!
gettimeofday(&wtstart,NULL);
do {
task = NULL;
if (!skipfirstpop)
nextr = gather_task(&task);
else skipfirstpop=false;
if (task == FF_GO_ON) continue;
channelid = (nextr-feedbackid);
frominput=true;
if (feedbackid>0) { // there are feedback channels
if (nextr<feedbackid) {
frominput=false;
channelid=nextr;
}
}
if ((task == FF_EOS) || (task == FF_EOSW)) {
if (filter && notify_each_eos)
filter->eosnotify(channelid); //workers[nextr]->get_my_id());
offline[nextr]=true;
++neos;
ret=task;
} else if (task == FF_EOS_NOFREEZE) {
if (filter && notify_each_eos)
filter->eosnotify(channelid); //workers[nextr]->get_my_id());
offline[nextr]=true;
++neosnofreeze;
ret = task;
} else {
FFTRACE(++taskcnt);
if (filter) {
FFTRACE(ticks t0 = getticks());
#if defined(FF_TASK_CALLBACK)
if (filter) callbackIn(this);
#endif
task = filter->svc(task);
#if defined(TRACE_FASTFLOW)
ticks diff=(getticks()-t0);
tickstot +=diff;
ticksmin=(std::min)(ticksmin,diff);
ticksmax=(std::max)(ticksmax,diff);
#endif
}
// if the filter returns EOS or GO_OUT we exit immediatly
if (task == FF_GO_ON) continue;
if ((task == FF_GO_OUT) || (task == FF_EOS_NOFREEZE) || (task == FF_EOSW) ) {
ret = task;
break; // exiting from the loop without sending the task
}
if (!task || (task == FF_EOS)) {
ret = FF_EOS;
break;
}
if (filter_outpresent) filter->ff_send_out(task);
else if (outpresent) push(task);
#if defined(FF_TASK_CALLBACK)
else
if (filter) callbackOut(this);
#endif
}
} while((neos<(size_t)running) && (neosnofreeze<(size_t)running));
// GO_OUT, EOS_NOFREEZE and EOSW are not propagated !
if (ret == FF_EOS) {
// we notify the filter only when we have received all EOSs
if (!notify_each_eos && filter) filter->eosnotify();
// push EOS
task = ret;
if (filter_outpresent) {
if (filter->isMultiOutput()) filter->propagateEOS();
else filter->ff_send_out(FF_EOS);
} else
if (outpresent) push(FF_EOS);
}
if (ret == FF_EOSW) ret = FF_EOS; // EOSW is like an EOS but it is not propagated
gettimeofday(&wtstop,NULL);
wttime+=diffmsec(wtstop,wtstart);
if (neos>=(size_t)running) neos=0;
if (neosnofreeze>=(size_t)running) neosnofreeze=0;
return ret;
}
/**
* \brief Finializes the gatherer.
*
* It is a virtual function used to finalise the gatherer task.
*
*/
virtual void svc_end() {
if (filter) filter->svc_end();
gettimeofday(&tstop,NULL);
}
int dryrun() {
running=workers.size();
if (filter) {
if ((filter->get_out_buffer() == nullptr) && buffer)
filter->registerCallback(ff_send_out_collector, this);
// setting the thread for the filter
filter->setThread(this);
assert(blocking_in==blocking_out);
filter->blocking_mode(blocking_in);
}
return 0;
}
/**
* \brief Execute the gatherer task.
*
* It executes the gatherer task.
*
* \return 0 if successful, otherwise -1 is returned.
*/
int run(bool=false) {
ff_gatherer::dryrun();
if (this->spawn(filter?filter->getCPUId():-1)== -2) {
error("GT, spawning GT thread\n");
return -1;
}
return 0;
}
void blocking_mode(bool blk=true) {
blocking_in = blocking_out = blk;
}
void no_mapping() {
default_mapping = false;
}
inline int wait_freezing() {
int r = ff_thread::wait_freezing();
running = -1;
return r;
}
/**
*
* \brief It gathers all tasks.
*
* It is a virtual function, and gathers results from the workers.
*
* \return It returns 0 if the tasks from all the workers are collected.
* Otherwise a negative value is returned meaning that at least an EOS has been received.
*
*/
virtual int all_gather(void *task, void **V) {
if (ag_callback) return ag_callback(task,V,ag_callback_arg);
V[channelid]=task;
size_t nw=getnworkers();
svector<ff_node*> _workers(nw);
for(ssize_t i=0;i<running;++i) {
if (!offline[i]) _workers.push_back(workers[i]);
else _workers.push_back(nullptr);
}
svector<size_t> retry(nw);
for(ssize_t i=0;i<running;++i) {
if(i != channelid) {
if (_workers[i]) {
if (!_workers[i]->get(&V[i])) retry.push_back(i);
}
}
}
while(retry.size()) {
channelid = retry.back();
if(_workers[channelid]->get(&V[channelid])) {
retry.pop_back();
}
else {
if (blocking_in) {
struct timespec tv;
timedwait_timeout(tv);
pthread_mutex_lock(cons_m);
pthread_cond_timedwait(cons_c, cons_m, &tv);
pthread_mutex_unlock(cons_m);
} else losetime_in();
}
}
bool eos=false;
for(ssize_t i=0;i<running;++i) {
if (V[i] == FF_EOS) {
eos=true;
++neos;
V[i]=nullptr;
FFTRACE(taskcnt--);
} else
if (V[i] == FF_EOS_NOFREEZE) {
eos=true;
++neosnofreeze;
V[i]=nullptr;
FFTRACE(taskcnt--);
}
}
FFTRACE(taskcnt+=nw-1);
return eos?-1:0;
}
void registerAllGatherCallback(int (*cb)(void *,void **, void*), void * arg) {
ag_callback = cb;
ag_callback_arg = arg;
}
/**
* \brief Thaws all threads register with the gt and the gt itself
*
*
*/
inline void thaw(bool _freeze=false, ssize_t nw=-1) {
assert(running==-1);
if (nw == -1 || (size_t)nw > workers.size()) running = workers.size();
else running = nw;
ff_thread::thaw(_freeze);
}
/**
* \brief Resets output buffer
*
* Warning resetting the buffer while the node is running may produce unexpected results.
*/
void reset() { if (buffer) buffer->reset();}
/**
* \brief Start counting time
*
* It defines the counting of start time.
*
* \return Difference in milli seconds.
*/
virtual double ffTime() {
return diffmsec(tstop,tstart);
}
/**
* \brief Complete counting time
*
* It defines the counting of finished time.
*
* \return Difference in milli seconds.
*/
virtual double wffTime() {
return diffmsec(wtstop,wtstart);
}
virtual const struct timeval & getstarttime() const { return tstart;}
virtual const struct timeval & getstoptime() const { return tstop;}
virtual const struct timeval & getwstartime() const { return wtstart;}
virtual const struct timeval & getwstoptime() const { return wtstop;}
#if defined(TRACE_FASTFLOW)
/**
* \brief The trace of FastFlow
*
* It prints the trace for FastFlow.
*
*/
virtual void ffStats(std::ostream & out) {
out << "Collector: "
<< " work-time (ms): " << wttime << "\n"
<< " n. tasks : " << taskcnt << "\n"
<< " svc ticks : " << tickstot << " (min= " << (filter?ticksmin:0) << " max= " << ticksmax << ")\n"
<< " n. push lost : " << pushwait << " (ticks=" << lostpushticks << ")" << "\n"
<< " n. pop lost : " << popwait << " (ticks=" << lostpopticks << ")" << "\n";
}
virtual double getworktime() const { return wttime; }
virtual size_t getnumtask() const { return taskcnt; }
virtual ticks getsvcticks() const { return tickstot; }
virtual size_t getpushlost() const { return pushwait;}
virtual size_t getpoplost() const { return popwait; }
#endif
private:
size_t max_nworkers;
ssize_t running; /// Number of workers running
ssize_t nextr;
size_t neos;
size_t neosnofreeze;
ssize_t channelid;
// if we have feedback channels in input, feedbackid tells us how many
// they are
ssize_t feedbackid;
ff_node * filter;
svector<ff_node*> workers;
svector<bool> offline;
FFBUFFER * buffer;
bool skip1pop;
#ifdef DFF_ENABLED
bool _skipallpop;
#endif
bool frominput;
int (*ag_callback)(void *,void **, void*);
void * ag_callback_arg;
struct timeval tstart;
struct timeval tstop;
struct timeval wtstart;
struct timeval wtstop;
double wttime;
protected:
// for the input queue
pthread_mutex_t *cons_m = nullptr;
pthread_cond_t *cons_c = nullptr;
// for the output queue
pthread_mutex_t *prod_m = nullptr;
pthread_cond_t *prod_c = nullptr;
// for synchronizing with the next multi-input stage
pthread_mutex_t *p_cons_m = nullptr;
pthread_cond_t *p_cons_c = nullptr;
bool blocking_in;
bool blocking_out;
#if defined(TRACE_FASTFLOW)
unsigned long taskcnt;
ticks lostpushticks;
unsigned long pushwait;
ticks lostpopticks;
unsigned long popwait;
ticks ticksmin;
ticks ticksmax;
ticks tickstot;
#endif
};
} // namespace ff
#endif /* FF_GT_HPP */