bazaar: CoWork clone with own Thread pool, AddThread,KillThread feature, WorkQueue a one-thread threadpool to offload work to be processed in order (see WorkQueueTest & WorkQueueTest2)

git-svn-id: svn://ultimatepp.org/upp/trunk@2624 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
kohait 2010-08-17 21:10:05 +00:00
parent e8a2be07ec
commit 00369e6875
20 changed files with 813 additions and 0 deletions

208
bazaar/CoWork/CoWork.cpp Normal file
View file

@ -0,0 +1,208 @@
#include "CoWork.h"
namespace MyCoWork
{
#ifdef _MULTITHREADED
#define LLOG(x) // LOG(x)
#define LDUMP(x) // DUMP(x)
CoWork::Pool& CoWork::pool()
{
static Pool pool;
return pool;
}
void CoWork::Pool::AddThread(int count)
{
lock.Enter();
for(int i = 0; i < count; i++)
{
Thread * pt = new Thread();
unsigned key = GetPtrHashValue(pt);
threads.Add(key, pt).Run(THISBACK1(ThreadRun, key));
}
lock.Leave();
}
void CoWork::Pool::KillThread(int count, bool waitone)
{
MJob job;
job.work = NULL;
bool finishall = false;
lock.Enter();
if(count < 0)
{
finishall = true;
jobs.Clear();
count = threads.GetCount();
}
count = min(count, threads.GetCount()); //clip
for(int i = 0; i < count; i++)
{
jobs.Add(job);
waitforjob.Release(); //threads will consume their Thread instances
}
lock.Leave();
//wait only when finishing
//scan the threads to find quited ones, if none available, handle over
if(waitone || finishall)
{
Sleep(1); //give chance for some to have finished, the waiting at least
lock.Enter();
int i = 0;
while(count > 0)
{
if(threads.GetKey(i) != 0)
{
//a used thread, check next
if(++i >= threads.GetCount())
{
i=0;
lock.Leave();
Sleep(1); //give chance for some to have finished
lock.Enter();
}
continue;
}
//a quited thread
Thread * pt = threads.Detach(i);
--count;
if(i >= threads.GetCount()) i=0;
lock.Leave();
//finish a Thread context
pt->Wait();
delete pt;
pt = NULL;
lock.Enter();
}
lock.Leave();
}
}
CoWork::Pool::Pool(int threadnr)
{
LLOG("CoWork INIT with threadnr " << threadnr);
if(threadnr < 0)
threadnr = CPU_Cores() + 2;
AddThread(threadnr);
}
CoWork::Pool::~Pool()
{
LLOG("Quit");
KillThread(-1); //all
LLOG("Quit ended");
}
bool CoWork::Pool::DoJob()
{
MJob job = jobs.Pop();
if(job.work == NULL) {
RLOG("Quit thread initiate");
return true;
}
lock.Leave();
job.cb();
lock.Enter();
if(--job.work->todo <= 0) {
LLOG("Releasing waitforfinish of (CoWork " << FormatIntHex(job.work) << ")");
job.work->waitforfinish.Release(); //multiple call, but semaphore cant become negative anyway
}
LLOG("Finished, remaining todo " << job.work->todo << " (CoWork " << FormatIntHex(job.work) << ")");
return false;
}
void CoWork::Pool::ThreadRun(unsigned tno)
{
LLOG("CoWork thread #" << tno << " started");
lock.Enter();
for(;;) {
while(jobs.GetCount() <= 0) {
waiting_threads++;
lock.Leave();
LLOG("#" << tno << " Waiting for job");
waitforjob.Wait();
LLOG("#" << tno << " Waiting ended");
lock.Enter();
}
LLOG("#" << tno << " Job acquired");
if(DoJob())
break;
LLOG("#" << tno << " Job finished");
}
int id = threads.Find(tno);
Thread & t = threads[id];
threads.SetKey(id, 0); //mark as quited
lock.Leave();
RLOG("!!! CoWork thread #" << tno << " finished");
}
void CoWork::Do(Callback cb) {
Pool& p = _pool;
p.lock.Enter();
if(p.jobs.GetCount() > 128) {
LLOG("Stack full: running in the main thread");
p.lock.Leave();
cb();
return;
}
MJob job;
job.cb = cb;
job.work = this;
p.jobs.Add(job);
todo++;
LLOG("Adding job; todo: " << todo << " (CoWork " << FormatIntHex(this) << ")");
if(p.waiting_threads>0) {
LLOG("Releasing thread waiting for job: " << p.waiting_threads);
p.waiting_threads--;
p.waitforjob.Release();
}
p.lock.Leave();
}
void CoWork::Finish() {
#ifdef _MULTITHREADED
Pool &p = _pool;
p.lock.Enter();
while(todo>0) {
LLOG("Finish: todo: " << todo << " (CoWork " << FormatIntHex(this) << ")");
if(p.jobs.GetCount()>0)
p.DoJob();
else {
p.lock.Leave();
LLOG("WaitForFinish (CoWork " << FormatIntHex(this) << ")");
waitforfinish.Wait();
p.lock.Enter();
}
}
p.lock.Leave();
LLOG("CoWork finished");
#endif
}
CoWork::CoWork(int threadnr)
: _pool((threadnr<0)?(pool()):( _opool.Attach(new Pool(threadnr)),_opool.operator*()))
{
LLOG("*** CoWork constructed " << FormatHex(this));
todo = 0;
}
CoWork::~CoWork()
{
Finish();
LLOG("~~~ CoWork destructed");
}
#endif
} //namespace

85
bazaar/CoWork/CoWork.h Normal file
View file

@ -0,0 +1,85 @@
#ifndef _CoWork_CoWork_h
#define _CoWork_CoWork_h
#include <Core/Core.h>
using namespace Upp;
namespace MyCoWork
{
#ifdef _MULTITHREADED
class CoWork : NoCopy {
struct MJob : Moveable<MJob> {
Callback cb;
CoWork *work;
};
struct Pool {
typedef Pool CLASSNAME;
Vector<MJob> jobs;
int waiting_threads;
ArrayMap<unsigned, Thread> threads;
CriticalSection lock;
Semaphore waitforjob;
Pool(int threadnr = -1);
~Pool();
void AddThread(int count = 1);
void KillThread(int count = 1, bool waitone = false);
bool DoJob();
void ThreadRun(unsigned tno);
};
friend struct Pool;
One<Pool> _opool; //local pool, needs to be here, before _pool, to be initialized before
static Pool& pool(); //global pool, switch in CoWork()
Pool & _pool; //this ref is actually used, set depending on threadnr
Semaphore waitforfinish;
int todo;
public:
void Do(Callback cb);
CoWork& operator&(Callback cb) { Do(cb); return *this; }
int GetThreadCount() const { return _pool.threads.GetCount(); }
int GetThreadCount() { _pool.lock.Enter(); int c = _pool.threads.GetCount(); _pool.lock.Leave(); return c; }
void AddThread(int count = 1) { ASSERT(&_pool != &pool()); _pool.AddThread(count); }
void KillThread(int count = 1, bool waitone = false) { ASSERT(&_pool != &pool()); _pool.KillThread(count, waitone); }
void Finish();
CoWork(int threadnr = -1);
~CoWork();
};
#else
class CoWork : NoCopy {
public:
void Do(Callback cb) { cb(); }
CoWork& operator&(Callback cb) { cb(); return *this; }
void Finish() {}
CoWork(int threadnr = -1) {}
};
#endif
class WorkQueue : public CoWork
{
public:
WorkQueue()
: CoWork(1)
{}
};
} //namespace
#endif

9
bazaar/CoWork/CoWork.upp Normal file
View file

@ -0,0 +1,9 @@
description "the custom CoWork which lets specify thread count of a new pool and a WorkQueue\377";
uses
Core;
file
CoWork.h,
CoWork.cpp;

1
bazaar/CoWork/Img.iml Normal file
View file

@ -0,0 +1 @@
PREMULTIPLIED

4
bazaar/CoWork/init Normal file
View file

@ -0,0 +1,4 @@
#ifndef _CoWork_icpp_init_stub
#define _CoWork_icpp_init_stub
#include "Core/init"
#endif

View file

@ -0,0 +1,178 @@
#include "WorkQueue.h"
namespace MyWQ
{
#ifdef _MULTITHREADED
#define LLOG(x) // LOG(x)
#define LDUMP(x) // DUMP(x)
WorkQueue::Pool& WorkQueue::pool()
{
// static Pool pool;
return _pool;
}
WorkQueue::Pool::Pool(int threadnr)
: jobcount(0), lastjob(_jobs.GetPtr())
{
LLOG("WorkQueue INIT with threadnr " << threadnr);
if(threadnr < 0)
threadnr = CPU_Cores() + 2;
for(int i = 0; i < threadnr; i++)
threads.Add().Run(THISBACK1(ThreadRun, i));
}
WorkQueue::Pool::~Pool()
{
LLOG("Quit");
//MJob job;
//job.work = NULL;
lock.Enter();
//jobs.Clear();
_jobs.DeleteList();
jobcount = 0;
//jobs.Add(job);
MJob & job = NextFree();
lock.Leave();
for(int i = 0; i < threads.GetCount(); i++)
waitforjob.Release();
for(int i = 0; i < threads.GetCount(); i++)
threads[i].Wait();
LLOG("Quit ended");
}
bool WorkQueue::Pool::DoJob()
{
MJob * list = _jobs.GetPtr();
MJob * e = list->GetNext();
ASSERT(e != list);
//if(jobs.Top().work == NULL) {
if(e->work == NULL) {
LLOG("Quit thread");
return true;
}
//MJob job = jobs.Pop(); //already done above
e->Unlink();
--jobcount;
if(e == lastjob)
lastjob = list; //so we knoe where to insert next
MJob & job = *e;
lock.Leave();
job.cb();
lock.Enter();
e->LinkBefore(list); //part of pop
if(--job.work->todo <= 0) {
LLOG("Releasing waitforfinish of (WorkQueue " << FormatIntHex(job.work) << ")");
job.work->waitforfinish.Release(); //multiple call, but semaphore cant become negative anyway
}
LLOG("Finished, remaining todo " << job.work->todo << " (WorkQueue " << FormatIntHex(job.work) << ")");
return false;
}
void WorkQueue::Pool::ThreadRun(int tno)
{
LLOG("WorkQueue thread #" << tno << " started");
lock.Enter();
for(;;) {
// while(jobs.GetCount() <= 0) {
while(jobcount <= 0) {
waiting_threads++;
lock.Leave();
LLOG("#" << tno << " Waiting for job");
waitforjob.Wait();
LLOG("#" << tno << " Waiting ended");
lock.Enter();
}
LLOG("#" << tno << " Job acquired");
if(DoJob())
break;
LLOG("#" << tno << " Job finished");
}
lock.Leave();
LLOG("WorkQueue thread #" << tno << " finished");
}
WorkQueue::MJob & WorkQueue::Pool::NextFree()
{
MJob *list = _jobs.GetPtr();
MJob *e = list->GetNext();
if(e != list && e->work == NULL) //as long as not end
{
//found a free one
e->Unlink();
e->LinkAfter(lastjob); //list
}
else
{
//need one more, either end or used
//e = list->InsertNext();
e = lastjob->InsertNext();
lastjob = e;
}
++jobcount;
return *e;
}
void WorkQueue::Do(Callback cb) {
Pool& p = pool();
p.lock.Enter();
// if(p.jobs.GetCount() > 128) {
if(p.jobcount > 128) {
LLOG("Stack full: running in the main thread");
p.lock.Leave();
cb();
return;
}
//MJob job;
MJob & job = p.NextFree();
job.cb = cb;
job.work = this;
//p.jobs.Add(job); //already done
todo++;
LLOG("Adding job; todo: " << todo << " (WorkQueue " << FormatIntHex(this) << ")");
if(p.waiting_threads>0) {
LLOG("Releasing thread waiting for job: " << p.waiting_threads);
p.waiting_threads--;
p.waitforjob.Release();
}
p.lock.Leave();
}
void WorkQueue::Finish() {
#ifdef _MULTITHREADED
Pool &p = pool();
p.lock.Enter();
while(todo>0) {
LLOG("Finish: todo: " << todo << " (WorkQueue " << FormatIntHex(this) << ")");
if(p.jobcount>0)
p.DoJob();
else {
p.lock.Leave();
LLOG("WaitForFinish (WorkQueue " << FormatIntHex(this) << ")");
waitforfinish.Wait();
p.lock.Enter();
}
}
p.lock.Leave();
LLOG("WorkQueue finished");
#endif
}
WorkQueue::WorkQueue()
: _pool(1)
{
LLOG("*** WorkQueue constructed " << FormatHex(this));
todo = 0;
}
WorkQueue::~WorkQueue()
{
Finish();
LLOG("~~~ WorkQueue destructed");
}
#endif
} //namespace

View file

@ -0,0 +1,77 @@
#ifndef _WorkQueue_WorkQueue_h_
#define _WorkQueue_WorkQueue_h_
#include <Core/Core.h>
using namespace Upp;
namespace MyWQ
{
#ifdef _MULTITHREADED
class WorkQueue : NoCopy {
struct MJob
: public Link<MJob>
, Moveable<MJob>
{
Callback cb;
WorkQueue *work;
MJob() : work(NULL) {}
};
struct Pool {
typedef Pool CLASSNAME;
//Vector<MJob> jobs;
LinkOwner<MJob> _jobs;
int jobcount;
MJob & NextFree();
Link<MJob> * lastjob;
int waiting_threads;
Array<Thread> threads;
CriticalSection lock;
Semaphore waitforjob;
Pool(int threadnr = -1);
~Pool();
bool DoJob();
void ThreadRun(int tno);
};
friend struct Pool;
//static Pool& pool();
Pool _pool;
Pool& pool();
Semaphore waitforfinish;
int todo;
public:
void Do(Callback cb);
WorkQueue& operator&(Callback cb) { Do(cb); return *this; }
void Finish();
WorkQueue();
~WorkQueue();
};
#else
class WorkQueue : NoCopy {
public:
void Do(Callback cb) { cb(); }
WorkQueue& operator&(Callback cb) { cb(); return *this; }
void Finish() {}
};
#endif
} //namespace
#endif

View file

@ -0,0 +1,9 @@
description "a thread WorkQueue implementation for serializeing off main thread work\377";
uses
Core;
file
WorkQueue.h,
WorkQueue.cpp;

View file

@ -0,0 +1,45 @@
#ifndef _WorkQueueTest_WorkQueueTest_h
#define _WorkQueueTest_WorkQueueTest_h
#include <CtrlLib/CtrlLib.h>
using namespace Upp;
#define LAYOUTFILE <WorkQueueTest/WorkQueueTest.lay>
#include <CtrlCore/lay.h>
#define IMAGEFILE <WorkQueueTest/WorkQueueTest.iml>
#include <Draw/iml_header.h>
#include <CoWork/CoWork.h>
class WorkQueueTest : public WithWorkQueueTestLayout<TopWindow> {
public:
typedef WorkQueueTest CLASSNAME;
WorkQueueTest();
void Work();
virtual void Paint(Draw& w);
virtual void LeftDown(Point p, dword keyflags);
virtual void Close()
{
//PostCallback(THISBACK(DoClose)); //results in deadlock, since PostCallback stuff is executed in GuiLock
//so the the worqueue cant complete. it's trying to do
//Refresh stuff in GuiLock environment, but cant,
//since the Postcallback is executing, and waiting for the thread to complete
//classical deadlock, mismatched order.. but is same with CoWork
Thread::Start(THISBACK(DoClose)); //so schedule Finish Wait in GuiLock free env.
}
void DoClose()
{
wq.Finish();
TopWindow::Close();
}
MyCoWork::WorkQueue wq;
int phase;
};
#endif

View file

@ -0,0 +1,7 @@
PREMULTIPLIED
IMAGE_ID(icon)
IMAGE_BEGIN_DATA
IMAGE_DATA(120,156,99,16,96,16,96,96,2,66,16,248,175,197,240,127,48,96,139,85,164,225,81,253,163,250,135,147,254,129,194,0)
IMAGE_DATA(130,184,0,83,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
IMAGE_END_DATA(64, 1)

View file

@ -0,0 +1,4 @@
LAYOUT(WorkQueueTestLayout, 412, 100)
ITEM(SliderCtrl, sl, LeftPosZ(0, 412).TopPosZ(76, 24))
END_LAYOUT

View file

@ -0,0 +1,15 @@
description "WorkQueue derived from custom CoWork\377";
uses
CtrlLib,
CoWork;
file
WorkQueueTest.h,
main.cpp,
WorkQueueTest.lay,
WorkQueueTest.iml;
mainconfig
"" = "GUI MT";

View file

@ -0,0 +1,5 @@
#ifndef _WorkQueueTest_icpp_init_stub
#define _WorkQueueTest_icpp_init_stub
#include "CtrlLib/init"
#include "CoWork/init"
#endif

View file

@ -0,0 +1,42 @@
#include "WorkQueueTest.h"
#define IMAGEFILE <WorkQueueTest/WorkQueueTest.iml>
#include <Draw/iml_source.h>
WorkQueueTest::WorkQueueTest()
{
CtrlLayout(*this, "Window title");
phase = 0;
sl.SetData(50);
}
void WorkQueueTest::Work()
{
Sleep(1000); //some loooong work to process
//show results
Size sz = GetSize();
(++phase) %= sz.cx;
Refresh();
}
void WorkQueueTest::LeftDown(Point p, dword keyflags)
{
//do something, and offload / queue independant work to the workqueue, it will be processed in order
//but in a worker thread, so main thread can react on user interaction (slider)
wq & THISBACK(Work);
}
void WorkQueueTest::Paint(Draw& w)
{
Size sz = GetSize();
w.DrawRect(0,0,sz.cx,sz.cy, SColorFace());
w.DrawText(0, 10, "Multiple Click left to queue work, use slider while processing");
w.DrawImage(phase, sz.cy/2, IMAGECLASS::icon());
}
GUI_APP_MAIN
{
WorkQueueTest().Run();
}

View file

@ -0,0 +1,44 @@
#ifndef _WorkQueueTest_WorkQueueTest_h
#define _WorkQueueTest_WorkQueueTest_h
#include <CtrlLib/CtrlLib.h>
using namespace Upp;
#define LAYOUTFILE <WorkQueueTest2/WorkQueueTest.lay>
#include <CtrlCore/lay.h>
#define IMAGEFILE <WorkQueueTest2/WorkQueueTest.iml>
#include <Draw/iml_header.h>
#include <WorkQueue/WorkQueue.h>
class WorkQueueTest : public WithWorkQueueTestLayout<TopWindow> {
public:
typedef WorkQueueTest CLASSNAME;
WorkQueueTest();
void Work(int i);
void WorkSelf();
void WorkQ();
virtual void Close()
{
//PostCallback(THISBACK(DoClose)); //results in deadlock, since PostCallback stuff is executed in GuiLock
//so the the worqueue cant complete. it's trying to do
//Refresh stuff in GuiLock environment, but cant,
//since the Postcallback is executing, and waiting for the thread to complete
//classical deadlock, mismatched order.. but is same with CoWork
Thread::Start(THISBACK(DoClose)); //so schedule Finish Wait in GuiLock free env.
}
void DoClose()
{
wq.Finish();
TopWindow::Close();
}
MyWQ::WorkQueue wq;
};
#endif

View file

@ -0,0 +1,8 @@
PREMULTIPLIED
IMAGE_ID(icon)
IMAGE_BEGIN_DATA
IMAGE_DATA(120,156,99,16,96,16,96,128,129,255,90,12,255,7,3,6,57,133,129,4,64,162,126,12,121,50,245,195,213,144,233,126)
IMAGE_DATA(74,245,195,205,192,162,31,197,124,42,233,71,119,215,127,2,250,209,205,192,166,159,129,128,126,92,102,16,10,127,108,238)
IMAGE_DATA(198,25,174,4,244,19,4,212,214,63,80,24,0,145,220,17,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
IMAGE_END_DATA(96, 1)

View file

@ -0,0 +1,7 @@
LAYOUT(WorkQueueTestLayout, 372, 320)
ITEM(DocEdit, inf, HSizePosZ(4, 4).VSizePosZ(4, 24))
ITEM(Button, workq, SetLabel(t_("WorkQ")).RightPosZ(0, 92).BottomPosZ(0, 20))
ITEM(Button, self, SetLabel(t_("Self")).RightPosZ(92, 100).BottomPosZ(0, 20))
ITEM(SliderCtrl, sl, LeftPosZ(4, 172).TopPosZ(296, 24))
END_LAYOUT

View file

@ -0,0 +1,15 @@
description "own WorkQueue test package\377";
uses
CtrlLib,
WorkQueue;
file
WorkQueueTest.h,
main.cpp,
WorkQueueTest.lay,
WorkQueueTest.iml;
mainconfig
"" = "GUI MT";

View file

@ -0,0 +1,5 @@
#ifndef _WorkQueueTest2_icpp_init_stub
#define _WorkQueueTest2_icpp_init_stub
#include "CtrlLib/init"
#include "WorkQueue/init"
#endif

View file

@ -0,0 +1,45 @@
#include "WorkQueueTest.h"
#define IMAGEFILE <WorkQueueTest/WorkQueueTest.iml>
#include <Draw/iml_source.h>
WorkQueueTest::WorkQueueTest()
{
CtrlLayout(*this, "Window title");
self <<= THISBACK(WorkSelf);
workq <<= THISBACK(WorkQ);
inf.Insert(0, "Cant use slider when Work Self, but can use it when offloading to workqueue");
sl.SetData(50);
}
void WorkQueueTest::WorkSelf()
{
inf.Clear();
for(int i = 0; i < 100; i++)
Work(i);
}
void WorkQueueTest::WorkQ()
{
inf.Clear();
for(int i = 0; i < 100; i++)
wq & THISBACK1(Work, i);
}
void WorkQueueTest::Work(int i)
{
Sleep(Random()%200); //some loong work to process
//show results
GuiLock __;
inf.Insert(inf.GetCursor(), String().Cat() << "-|" << i << "|-");
inf.SetCursor(inf.GetLength());
}
GUI_APP_MAIN
{
WorkQueueTest().Run();
}