前言
在1中,我們實現了一個基礎的signal+slot的模塊件調用機制,不過那個direct調用,在這篇中,我們將支持誇線程調用,即在對象的線程上下文中,調用對象的函數。
對象線程綁定
在qt中,有一個很重要的概念,對象線程綁定,即在每個對象實例時候,其保存其線程上下文信息,其中就有一個事件循環,跨線程的信號就是將這個一個task丟到這個事件循環中,在對象綁定的線程中完成slot響應。
實現
對slot調用的打包-Task
我們需要將一個跨線程的slot調用打包成一個task,然後丟到想相應的事件循環中。在事件循環中需要維護一個task隊列,那第一還是需要做容器。
struct TaskBase
{
virtual ~TaskBase() = 0;
virtual void run() = 0;
};
TaskBase::~TaskBase()
{
}
TaskBase
爲我們Task基類,在事件循環中,維護其一個鏈表。
我們繼承它,實現我們第一個Task,一個一般的Task。
template <typename Method>
struct NormalTask : public TaskBase
{
typedef typename ParameterTupleTraits<ParameterTraits<Method>>::Parameters Parameters;
typedef typename TypeTraits<typename ParameterTraits<Method>::classType_value>::pointerType_value type_value;
NormalTask(type_value object, Method m, Parameters paras) : TaskBase(),
m_object(object),
m_method(m),
m_paras(paras)
{
}
virtual ~NormalTask() {}
void run()
{
printf("run:%lu\n", pthread_self());
impleDoTask(m_object, m_method, m_paras);
}
type_value m_object;
Method m_method;
Parameters m_paras;
};
其實一個模板,於具體的對象成員函數相關,接受,對象指針,對象成員函數指針和參數。pthread_self可以打印當前線程的標識,其實一個unsigned long類型的數,這裏爲了看其的執行線程。
事件循環和對象綁定
爲了實現對象線程綁定,我們必須提供一個在這套系統上的基類,正如qt中的QObject。
struct Box
{
Box()
{
m_loop = EventLoop::currentEventLoop();
}
EventLoop *m_loop;
};
下面開始我們的事件循環,其實這隻有一個任務隊列,實際中其還必須能夠監聽系統各種事件,比如按鍵,鼠標,加其發送給合適的對象,就是我的一篇博客介紹的事件監聽分發,這裏不做討論。
static EventLoop *currentEventLoop()
{
EventLoop *re = NULL;
pthread_t pid = pthread_self();
std::map<pthread_t, EventLoop *>::iterator ite = s_eventloops.find(pid);
if (ite != s_eventloops.end())
{
re = ite->second;
}
else
{
re = new EventLoop(pid);
s_eventloops.insert(std::pair<pthread_t, EventLoop *>(pid, re));
}
return re;
}
static void exit()
{
for (auto ite = s_eventloops.begin(); ite != s_eventloops.end(); ++ite)
{
ite->second->quit();
delete (ite->second);
}
s_eventloops.clear();
}
int exec()
{
while (run)
{
for (auto ite = m_tasks.begin(); ite != m_tasks.end();)
{
(*ite)->run();
delete (*ite);
ite = m_tasks.erase(ite);
}
//sleep(10);
}
return 1;
}
void quit()
{
run = false;
}
};
std::map<pthread_t, EventLoop *> EventLoop::s_eventloops = std::map<pthread_t, EventLoop *>();
怎麼實現投遞
提供了一個枚舉,來設置連接類型
enum CONNECT_TYPE
{
SYNC,
AYNC,
AUTO
};
對應:同步,異步和系統決定。
在原來signal
eemit時,我們必須實現區別發送
void eemit()
{
printf("eemit:%lu\n", pthread_self());
std::tuple<> para;
for (typename listValue_type::iterator ite = sloters.begin(); ite != sloters.end(); ++ite)
{
CONNECT_TYPE type = (*ite)->m_type;
switch (type)
{
case SYNC:
{
if (EventLoop::currentEventLoop() == ((*ite)->eventloop()))
{
(*ite)->dotask(para);
}
else
{
(*ite)->eventloop()->postNormalTask((*ite)->convertAnormalTask(para));
}
};
break;
case AYNC:
{
(*ite)->eventloop()->postNormalTask((*ite)->convertAnormalTask(para));
};
break;
case AUTO:
{
if (EventLoop::currentEventLoop() == ((*ite)->eventloop()))
{
(*ite)->dotask(para);
}
else
{
(*ite)->eventloop()->postNormalTask((*ite)->convertAnormalTask(para));
}
};
break;
default:
break;
}
}
}
爲了實現在eemit中根據對象的線程綁定來區分行爲,但是我們得到的對象,只是一個基類指針,所以對原來的基類進行了改造。
template <typename Paras>
struct SlotBase
{
SlotBase(CONNECT_TYPE type) : m_type(type)
{
}
virtual TaskBase *convertAnormalTask(Paras paras) = 0;
virtual EventLoop *eventloop() = 0;
virtual ~SlotBase() = 0;
virtual void dotask(Paras paras) = 0;
CONNECT_TYPE m_type;
};
template <typename Paras>
SlotBase<Paras>::~SlotBase()
{
}
在slot實現這連個純虛函數,來得到打包好的Task和得到槽對象的事件循環指針。
TaskBase *convertAnormalTask(Parameters paras)
{
return new NormalTask<M>(m_object, m_method, paras);
}
EventLoop *eventloop()
{
return m_object->m_loop;
}
這樣保證了在eemit時獲得正確的task和事件循環,當槽對象的eventloop和eemit的對象不在一個線程中,我們就將task投遞到槽對象的eventloop中。
爲了實現想下列的使用
EventLoop *loop = EventLoop::currentEventLoop();
loop->postNormalTask(NewNormalTask(&object, &A::func_b, 1314));
我們提供了NewNormalTask各版本
template <typename Method>
TaskBase *NewNormalTask(typename TypeTraits<typename ParameterTraits<Method>::classType_value>::pointerType_value object,
Method m, typename ParameterTraits<Method>::P0 p0,
typename ParameterTraits<Method>::P1 p1,
typename ParameterTraits<Method>::P2 p2,
typename ParameterTraits<Method>::P3 p3,
typename ParameterTraits<Method>::P4 p4,
typename ParameterTraits<Method>::P5 p5)
{
StaticCheck<ParameterTraits<Method>::size == 6> a;
UNUSE(a);
typename ParameterTupleTraits<ParameterTraits<Method>>::Parameters paras = std::make_tuple(p0, p1, p2, p3, p4, p5);
return new NormalTask<Method>(object, m, paras);
}
運用實例
#include <iostream>
#include "signal.h"
using namespace std;
struct TTT
{
int a;
int b;
int c;
};
class N : public Box
{
public:
virtual void func_c(int a, int b) = 0;
};
class A : public N
{
public:
A()
{
s1.connect(this, &A::func_ii);
}
void func_b(int a) {cout << a << "aaaaaa\n";}
void func_c(int a, int b) {cout << a << "+" << b << "=" << a+b << std::endl;}
void func_a()
{
TTT t = {1, 's', 't'};
s.eemit(t, t, t);
}
void func_i()
{
int *i = new int(1);
s1.eemit(i);
}
void func_ii(int *i)
{
cout << *i;
(*i)++;
delete i;
}
void func_z()
{
cout << "zhou xiang ";
}
Signal<void (*)(TTT, TTT, TTT)> s;
Signal<void (*)(int*)> s1;
};
class B : public Box
{
public:
B(A *a):m_a(a){}
B(){}
void func_b(int a) {cout << a << "bbbbbbb\n";}
void func_slot(TTT t, TTT t2, TTT t3)
{
cout << t.a + t2.b + t3.c << "-==-=-=-==-=\n";
}
void func_z()
{
cout << "love chenchen\n";
}
A *m_a;
void func_e()
{
m_a->s.connect(this, &B::func_slot);
}
};
void* pt_callback(void * arg)
{
A aa;
B bb;
Signal<void (*)()> *s = (Signal<void (*)()>*)(arg);
s->connect(&aa, &A::func_z);
s->connect(&bb, &B::func_z);
EventLoop *loop = EventLoop::currentEventLoop();
loop->exec();
return NULL;
}
int main()
{
A object;
B object1(&object);
Signal<void (*)(int, int)> s;
Signal<void (*)()> ssss;
// ssss.connect(&object, &A::func_z);
// ssss.connect(&object1, &B::func_z);
// ssss.connect(&object1, &B::func_z);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// s.connect(&object ,&A::func_c);
// object1.func_e();
// object1.func_e();
// object.func_a();
//ssss.eemit();
// ssss.disconnect(&object, &A::func_z);
// ssss.disconnect(&object1, &B::func_z);
//object.func_i();
//cout << "---------------------------\n";
//ssss.eemit();
//s.eemit(4, 456);
// EventLoop *loop = EventLoop::currentEventLoop();
// loop->postNormalTask(NewNormalTask(&object, &A::func_b, 1314));
//std::cout << "+++++++++++++++++++++\n";
pthread_t ptid;
int ptre = pthread_create(&ptid, NULL, pt_callback, (void *)(&ssss));
if (ptre != 0)
{
printf("dispatcher thread create fail!\n");
}
ssss.eemit();
// ssss.disconnect(&object, &A::func_z);
// ssss.disconnect(&object1, &B::func_z);
//object.func_i();
cout << "---------------------------\n";
ssss.eemit();
EventLoop *loop = EventLoop::currentEventLoop();
loop->postNormalTask(NewNormalTask(&object, &A::func_b, 1314));
loop->exec();
return 0;
}
運行結果
在操作隊列的時候,沒有加鎖。還有就是線程之間執行不可控,在eemit時,在pt_callback中,connect還沒有完成,導致,有時有兩個響應有時一個,線程向標準輸出寫入的時候也是不可控的,所有log有點難看,但還是說明了問題,操作在對象的線程上下問中進行。
項目代碼可以在1中找到地址。