The
Kahless_9 framework
c++ is already
widely considered a very powerful language that provides very good
performance and when combined with multi-threaded design can offer
much more power and performance. The main issue with c++ however is
that it is still consider by many to be a difficult language to use
correctly relative to other languages like java and c# which are
considered managed.
Being a developer
with capabilities in all these languages I can agree that c++ is more
complicated than java or c# but my agreement comes with some
reservations. I consider using c++ over java similar to someone
using helicopter instead of a car to reach a particular destination.
Most people consider a car easier to operate than a helicopter and
rightfully so but making the complete trip with the car to reach
one's destination might not be as easy and effective as doing it with
a helicopter. Basically the car would make a good easy start but
could very well end up not completing the journey or really battle to
do so, whereas the helicopter will take more effort to get going but
will most likely complete the journey efficiently.
This is similar in my view to using c++ over a managed language like java or c#. Please note that I am not trashing java or c# but am advocating for a greater use of c++ where it can be highly beneficial. Too often we hear reasons why c++ is not the best choice and this is evident by the much larger java and c# community as well as the belief over the last 10~15 years that c++ is going to be replaced by one of these other languages. Today, I will try to do the opposite by advocating some reasons why to choose c++ and dispel some of the reason people believe for rejecting it.
One reason I
heard from a work colleague about why a particular component of a
system was developed in vb instead of c++ was that c++ is too
difficult. As mentioned before, I can agree that c++ is more
difficult than other languages especially vb, but strangely here too,
my car verses helicopter analogy is befitting. My colleague hastily
used vb like a car to start a journey but found that journey
eventually becoming too difficult to complete with a car and then I
was called in with the c++ helicopter which needless to say completed
the journey with overwhelming success.
The trick is to
make c++ simple for yourself and this may be a tip that would help:
do a basic c++ course that comes with good examples that relay the
languages basics clearly. Then pick a c++ framework that fits he
specific area of work you will be, or are in already. For front end
development, Qt is probably one of the best ranked out there. For
back end server type development, I can recommend Kahless_9.
Kahless_9 is a
c++ framework consisting of multiple class libraries that exist for
both windows and linux. The framework was recently released at site
http://www.shankodev.com but has been collaboratively developed in
various stages over the last 15 years by a group of specialist
working in particular fields where high performance through
multi-threading was critical to success.
The site also
recently listed that it will be releasing Kahless_9 in dot NET
versions for the visual studio 9 and 12 versions of the c++
framework. This means that if you know Kahless_9 c++ you will be
able to easily apply the same concepts in c# or vb.net or
even c++_cli. The reverse as well should be true in that a c# or
vb.net developer that knows the Kahless_9 framework will also easily
be able to adapt same concepts for c++.
Aside from the
rich repository of threading abstractions available in Kahless_9, it
also contains abstractions worth mentioning quickly that unifies
certain structures and mechanisms in both linux and windows. Some
examples of these are the date and time class KDate, the guid class
and the file system management classes. Another striking feature of
Kahless_9 is its ability to demonstrate multi-threaded action very
quickly and easily in a frame that can easily be adapted for actual
production use. This is partly accomplished by Kahless_9’s logging
class which is thread-safe and logs the date and time down to
millisecond as well as thread id of a particular log entry.
Consider the
following sample that demonstrates using a logging class within two
threads:
#include
<conio.h>
#include
"KLibIncUse.h"
#include
"KLibThreadIncUse.h"
KLog
Log("Logs\\Pxrs.log");
/************************************************************************************
**
**
CPxrA
**
************************************************************************************/
class
CPxrA : public
KThreadProcessor
{
public:
CPxrA()
: m_nCntA(0 )
{
SetCycleSleepTime(3000);
}
~CPxrA()
{
Stop();
}
void
Process()
{
Log("m_nCntA
= [%d]", m_nCntA);
m_nCntA
+= 3;
}
private:
int
m_nCntA;
};
/*************************************************************************************
**
**
CPxrB
**
*************************************************************************************/
class
CPxrB : public
KThreadProcessor
{
public:
CPxrB()
: m_nCntB(2)
{
SetCycleSleepTime(2000);
}
~CPxrB()
{
Stop();
}
void
Process()
{
Log("m_nCntB
= [%d]", m_nCntB);
m_nCntB
+= 2;
}
private:
int
m_nCntB;
};
/*************************************************************************************
**
**
main
**
*************************************************************************************/
int
main()
{
Log.LogHeader();
CPxrA
pxrA;
CPxrB
pxrB;
pxrA.Start();
pxrB.Start();
printf("Press
any key to stop processing ... ");
getch();
printf("\nKey
was pressed, now stopping pxrs\n");
pxrA.Stop();
pxrB.Stop();
printf("pxrs
has been stopped\n");
return
0;
}
The following is
typical output for the above program:
<2016-12-22
01:49:10.571> [00000330]
*************************************************************
<2016-12-22
01:49:10.571> [00000330]
*************************************************************
<2016-12-22
01:49:10.571> [00000330] ***
<2016-12-22
01:49:10.571> [00000330] *** Log Header:
<2016-12-22
01:49:10.571> [00000330] *** File: [multi_thread_log_tst]
<2016-12-22
01:49:10.571> [00000330] *** Version: [No Version]
<2016-12-22
01:49:10.571> [00000330] *** Name: [Free Demo Version]
<2016-12-22
01:49:10.571> [00000330] *** Company: [Free Demo Version]
<2016-12-22
01:49:10.571> [00000330] *** License: [Free Demo Version]
<2016-12-22
01:49:10.571> [00000330] ***
<2016-12-22
01:49:10.586> [00000330]
*************************************************************
<2016-12-22
01:49:10.586> [00000330]
*************************************************************
<2016-12-22
01:49:10.586> [00000930] m_nCntA = [0]
<2016-12-22
01:49:10.602> [00000adc] m_nCntB = [2]
<2016-12-22
01:49:12.614> [00000adc] m_nCntB = [4]
<2016-12-22
01:49:13.597> [00000930] m_nCntA = [3]
<2016-12-22
01:49:14.627> [00000adc] m_nCntB = [6]
<2016-12-22
01:49:16.608> [00000930] m_nCntA = [6]
<2016-12-22
01:49:16.639> [00000adc] m_nCntB = [8]
<2016-12-22
01:49:18.652> [00000adc] m_nCntB = [10]
<2016-12-22
01:49:19.619> [00000930] m_nCntA = [9]
<2016-12-22
01:49:20.664> [00000adc] m_nCntB = [12]
<2016-12-22
01:49:22.630> [00000930] m_nCntA = [12]
<2016-12-22
01:49:22.677> [00000adc] m_nCntB = [14]
<2016-12-22
01:49:24.689> [00000adc] m_nCntB = [16]
<2016-12-22
01:49:25.641> [00000930] m_nCntA = [15]
<2016-12-22
01:49:26.701> [00000adc] m_nCntB = [18]
<2016-12-22
01:49:28.651> [00000930] m_nCntA = [18]
<2016-12-22
01:49:28.714> [00000adc] m_nCntB = [20]
<2016-12-22
01:49:30.726> [00000adc] m_nCntB = [22]
<2016-12-22
01:49:31.662> [00000930] m_nCntA = [21]
<2016-12-22
01:49:32.739> [00000adc] m_nCntB = [24]
<2016-12-22
01:49:34.673> [00000930] m_nCntA = [24]
<2016-12-22
01:49:34.751> [00000adc] m_nCntB = [26]
<2016-12-22
01:49:36.763> [00000adc] m_nCntB = [28]
<2016-12-22
01:49:37.684> [00000930] m_nCntA = [27]
<2016-12-22
01:49:38.776> [00000adc] m_nCntB = [30]
<2016-12-22
01:49:40.695> [00000930] m_nCntA = [30]
<2016-12-22
01:49:40.788> [00000adc] m_nCntB = [32]
Explanation:
The sample
consists of a main function and two classes [CPxrA] and [CPxrB].
Each of these classes inherit from [KThreadProcessor] which requires
its derived classes to implement a [Process] method. Each of these
[Process] methods run within their own KThreadProcessor’s thread
instance cyclically. The sleep time in milliseconds between each
cycle can be set via KThreadProcessor’s [SetCycleSleepTime]. In
our sample [CPxrA]’s sleep cycle time is set to 3 seconds while
[CPxrB]’s sleep time is set to 2 seconds. This means that [pxrA]’s
Process method will execute every 3 seconds while [CPxrB]’s Process
method will execute every 2 seconds.
Both pxrA and
pxrB simply increment their local members [m_nCntA] and [m_nCntB]
respectively after logging previous value. pxrA increments its value
by 3 each cycle while pxrB increments its value by 2. The main
function owns the pxrA and pxrB objects which it simply calls [Start]
on. Thereafter the main function waits for us to press any key in
order to call [Stop] on pxrA and pxrB which will stop the [Process]
methods of these two objects from executing.
The log file
produced demonstrates the date and time down to millisecond of each
log as well as the thread id responsible for creating the log entry.
From the log extract above we can see that pxrA’s thread id is
[00000930] while pxrB’s thread id is [00000adc]. This is very
important in multi-threaded development as it provides a visual
detail of the how the various threads involved performed. Typically,
such logs become your first and most valuable tool for solving
multi-threaded related issues.
Another
Approach
The results of
the previous sample can also be attained by using the [KPxr]
threading abstraction to create 2 individual threads within a single
class that cyclically processes two separate methods of the class.
This can be observed in the following code listing:
#include
<conio.h>
#include
"KLibIncUse.h"
#include
"KLibThreadIncUse.h"
KLog
Log("Logs\\Pxrs.log");
/***************************************************************************************
**
**
CPxr
**
***************************************************************************************/
class
CPxr
{
public:
CPxr()
: m_nCntA(0), m_nCntB(0)
{
m_pxrA.Activate(this,
MyProcessA, 3000);
m_pxrB.Activate(this,
MyProcessB, 2000);
}
~CPxr()
{
Stop();
}
void
Start()
{
m_pxrA.Start();
m_pxrB.Start();
}
void
Stop()
{
m_pxrA.Stop();
m_pxrB.Stop();
}
void
MyProcessA()
{
Log("m_nCntA
= [%d]", m_nCntA);
m_nCntA+=3;
}
void
MyProcessB()
{
Log("m_nCntB
= [%d]", m_nCntB);
m_nCntB+=2;
}
private:
int
m_nCntA;
int
m_nCntB;
KPxr<CPxr>
m_pxrA;
KPxr<CPxr>
m_pxrB;
};
/**************************************************************************************
**
**
main
**
***************************************************************************************/
int
main()
{
Log.LogHeader();
CPxr
pxr;
pxr.Start();
printf("Press
any key to stop processing ... ");
getch();
printf("\nKey
was pressed, now stopping pxrs\n");
pxr.Stop();
printf("pxrs
has been stopped\n");
return
0;
}
Explanation
Notice that
unlike in the previous sample that had two classes namely [CPxrA] and
[CPxrB], this sample only has one called [CPxr]. Class [CPxr]
contains two methods called [MyProcessA] and [MyProcessB] which gets
called cyclically by the two [KPxr] instances [m_pxrA] and [m_pxrB].
Within the constructor of [CPxr] we setup [m_pxrA] to process method
[MyProcessA] and [m_pxrB] to process [MyProcessB] by calling their
[Activate] methods to which we pass the method to be executed. The
third parameter of the [Activate] method refers to the cycle sleep
time for the particular processor. In the above sample we set
[m_pxrA] to sleep for 3000 milliseconds while we instruct [m_pxrB] to
sleep for 2000 milliseconds.
A Good Date
and Time class
One thing a c++
developer tends to notice is that there are many date time structures
to choose from, each with its own pro’s and conns. At times one
may even find that what you use in windows might not be in linux or
even the other way around. To this end, the framework provides a
good date time class called [KDate], which can handle nearly all
other date time structures and can represent a vaster time range than
most other structures. This class also makes date time duration
arithmetic very easy. Observe the following sample:
#include
"KLibIncUse.h"
int
main()
{
KDate
dtX(2016, 12, 21, 23, 37, 54); //
21-Dec-2016 23:37:54
KDate
dtY = dtX + 3*__1hour + 23*__1min + 6*__1sec; //
3 hrs, 23 mins & 6 secs after dtX
KDate
dtZ = dtY - 7.0; //
1 week before dtY
cout
<< "dtX = [“ << dtX.ToString().sz() << “]”
<< endl;
cout
<< "dtY = [“ << dtY.ToString().sz() << “]”
<< endl;
cout
<< "dtZ = [“ << dtZ.ToString().sz() << “]”
<< endl;
return
0;
}
The
sample above will output the following:
dtX
= [2016-12-21 23:37:54]
dtY
= [2016-12-22 03:01:00]
dtZ
= [2016-12-15 03:01:00]
Press
any key to continue
File
Management System classes
There are often
cases within a system that one would need to perform some type of
file management operations. Given that standard c++ does not cater
for this it is fortunate that Kahless_9 comes equipped with such
mechanisms to enabled uniformity in this regard for linux and
windows. In essence the framework contains the classes [KFileIO],
[KDirTraverser] and [KDirLister] to accomplish file management.
Class [KFileIO] is handy for extracting the filename or file
extension or returning the size of a file while the other two classes
are handy for returning a directory listing and scanning through a
directory tree and performing operations on the files and
subdirectories found. Most ad-hoc development requiring a level of
file management can easily be developed by using these three classes
mentioned. The framework in particular demonstrates this by
providing source code for utilities like [dcx], [fcx] and
[find_files_btween_time] among several others. Let us review the
source code for [find_files_between_time]:
class
CConsoleMain
{
public:
CConsoleMain();
~CConsoleMain();
void
ShowUsage();
void
ParseArgs(int
argc, char
* argv[]);
void
Process();
private:
KStr
m_strFileSpec;
KDate
m_dtStart;
KDate
m_dtEnd;
bool
m_bProcessSubDirs;
static
void
_FuncDirTrv(const
KStr
& strDir, const
_finddata_t & fi, void
* pvInst);
};
void
CConsoleMain::ParseArgs(int
argc, char
* argv[])
{
…
m_strFileSpec
= argv[1];
m_dtStart.FromString(argv[2]);
m_dtEnd.FromString(argv[3]);
}
void
CConsoleMain::Process()
{
KDirTraverser
dirTrv;
dirTrv.SetFileSpec(m_strFileSpec);
dirTrv.SetRecurtSubDirs(m_bProcessSubDirs);
dirTrv.SetFileExecInfoFunc(_FuncDirTrv,
this);
dirTrv.Execute();
}
void
CConsoleMain::_FuncDirTrv(const
KStr
& strDir, const
_finddata_t & fi, void
* pvInst)
{
CConsoleMain
* inst = (CConsoleMain *)pvInst;
KDate
dtCreate(fi.time_create);
KDate
dtModify(fi.time_write);
bool
bCreateIn = dtCreate>=inst->m_dtStart &&
dtCreate<=inst->m_dtEnd;
bool
bModifyIn = dtModify>=inst->m_dtStart &&
dtModify<=inst->m_dtEnd;
if
(bCreateIn || bModifyIn)
{
printf("
[%s] [%s] - %s\n", dtCreate.ToString().sz(),
dtModify.ToString().sz(),
KFileIO::Join(strDir,
fi.name).sz());
}
}
Explanation
The
important methods to take note of for class [ConsoleMain] are
[Process] and [_FuncDirTrv]. The [Process] method creates a
[KDirTraverser] and sets the file specification to be searched for as
well as the callback function [_FuncDirTrv] which it uses to process
each file received by the directory traverser. The callback function
[_FuncDirTrv] checks if the files create or modify time falls between
the start end time specified and outputs to screen if it does. Even
though this version of [KDirTraverser] accepting a callback function
passing the [_finddata_t] file structure is windows specific, we can
apply a similar solution for linux where we implement the callback
function that passes the filename as a string. Thereafter our linux
version of the callback function can obtain the files create and
modify times by using [KFileIO].
Networking in
Kahless_9
Development of
network based application can be accomplished by using the frameworks
socket abstractions for udp or tcp sockets. The socket abstractions
consist of wrappers that encapsulate the functionality of udp and tcp
sockets as well as a wrapper to simplify the handling of a socket
address. Simply put, these abstractions will allow a developer to
create a udp or tcp based socket application but without the need to
know about or get into the messy syntax that is normally accompanied
with socket based applications. The classes to take note of here
are: [KSockUDP], [KSockTCP] and [KSockIp], and the examples available
for download will demonstrate just how easy and neat these classes
make socket application development. There are in addition other
networking abstractions as well that further simplifies network based
development and can provide performance that is not easily attained
with normal socket level development. One of these classes is called
[KTransmissionMgr] while the other is called [KTransmissionMgrHL].
Both these classes handle [KTransmission] objects with the only
difference between them being that the [KTransmissionMgrHL] is
intended for higher loads of networking traffic. The
[KTransmissionMgr] basically uses a balanced tree (a 2-3 tree) in
order to store its transmissions while the [KTransmissionMgrHL] uses
a [K23TreeOrb] which does not suffer the same performance hit for a
delete operation as does the normal 2-3 tree. Although the
[KTranmissionMgr] suffers slightly when encountering higher loads
where a higher rate of delete operations are required, it is by no
means slow, the high load version just performs even better when
encountering high loads. To demonstrate its use, consider the
following code sample:
unsigned
short
usPort = 3773;
KTransmissionMgr
mgr;
mgr.Activate(usPort);
mgr.SetFuncHndlTxmRx(FuncTxmRx,
NULL);
…
void
FuncTxmRx(KTransmissionRx * pTxmRx, void
* pvParam)
{
unsigned
char
* pBuffer = pTxmRx->GetBuffer();
unsigned
int
uiSize = pTxmRx->GetBufferSize();
//
now process the receive buffer as you wish
}
For
illustration, let us consider the above snippet to be the server
based application while the next the client based. Technically
however since both the client and server both contain
[KTransmissionMgr] objects they are both equally capable of
transmitting and receiving which actually makes their underlying
architecture peer-to-peer as opposed to a strict client server model.
The client code snippet is as follows:
unsigned
short
usPort = 3775;
KStr
strText
= “This is a text buffer to test KTranssmissionMgr”;
KTransmissionMgr
mgr;
mgr.Activate(usPort);
mgr.Transmitt(“127.0.0.1”,
3773, strText.sz(), strText.GetLength());
Explanation
In both snippets
listed above the [KTransmissionMgr] objects call their [Activate]
methods which tell it which port to listen on. The [Activate]
methods also allow other values for controlling the number of
receiving sockets, number of transmitting sockets, number of threads
per socket as well as the guid with which the [KTransmissionMgr]
identifies itself.
In the server
snippet above we set a callback function by calling
[SetFuncHndlTxmRx] to handle incoming transmissions. The callback
function [FuncTxmRx] has form:
void
FuncName(KTransmissionRx * pTxmRx, void * pvParam);
The client based
snippet above calls its [Transmitt] method passing to it the ip
address and port to where transmission should be sent as well as the
buffer along with its size to transmit. We could have also setup
callback functions to handle transmission success or failures from
both tx and rx perspectives.
Synchronization
Synchronization
has many forms each of which attempts to sequence action to a
particular resource such that incoherent access is limited thus
preventing race conditions or other threading concurrency related
issues. Some of these synchronization constructs are heavy weight
while some are light weight. A mutex can be considered a heavy
weight synchronization construct since it applies a full lock across
a process barrier and only releases lock when it is completely done
with it thus causing complete blocking on all other waiting
thread/process instances. A Critical Section on the other hand can
be considered a lighter weight synchronization construct than a mutex
as it only applies its lock between threads and not across process
barriers but is heavier than a simple [KLock] that does not cater for
a lock being applied more than once from same thread without being
released first.
The best
synchronization constructs to use would be the lightest ones where
possible. Kahless_9 has some good lightweight synchronization
constructs that if applied correctly within a solution would provide
excellent overall performance. One such synchronization construct to
consider is the [KSafeReadItem] which allows multiple readers to
simultaneously read a resource while a single writer can update it
cyclically without any locking/waiting. Consider the following
sample:
struct
SSomeStockTbls
{
KDate
dtUpdateTime;
… //
all other stock tbl values
};
SomeStockTblsMgr.h
class
CSomeStockTblsMgr
: public
KThreadProcessor
{
public:
CSomeStockTblsMgr();
void
Process();
SSomeStockTbls
* PtrTbls();
private:
KSafeReadItem<SSomeStockTbls>
m_sriStockTbls;
void
_LoadNewStockTbls(SSomeStockTbls & tbls);
};
SomeStockTblsMgr.cpp
CSomeStockTblsMgr::CSomeStockTblsMgr()
{
SetCycleSleepTime(5*60*1000);
//every
5 minutes
}
void
CSomeStockTblsMgr::Process()
{
SSomeStockTbls
tbls;
_LoadNewStockTbls(tbls);
m_sriStockTbls.SetItem(tbls);
m_sriStockTbls.SwitchCurrentPtr();
}
SSomeStockTbls
* CSomeStockTblsMgr::PtrTbls() //
concurrent multiple read thread access
{
return
m_sriStockTbls.GetCurrentPtr();
}
void
CSomeStockTblsMgr::_LoadNewStockTbls(SSomeStockTbls & tbls)
{
tbls.dtUpdateTime
= KDate::Now();
//
complete rest of load from ftp, internet, ...
}
Explanation
We have some structure [SSomeStockTbls] that contains the update time
among other records relevant to some business model. The class
[CSomeStockTblsMgr] for managing updates and access to this structure
stores a [KSafeReadItem] of it which is updated every five minutes in
its [Process] method in its own thread inherited from
[KThreadProcessor]. A [KSafeReadItem] stores 2 instances of the item
type specified and keeps track of the current and noncurrent one.
All reads/gets are done from the current item while the sets are all
done to the noncurrent item. The [Process] method updates a local
stack based instance of the structure called [tbls] by calling method
[_LoadNewStockTbls]. Thereafter the [tbls] parameter is set to the
safe read item [m_sriStockTbls] which will set the safe read items
noncurrent instance of structure. After the call to
[SwitchCurrentPtr] all new read threads will receive the newly
updated structure while all other completing (trans phasing) read
threads will be allowed to safely complete their already busy read
operations on the old structure. Note that this model allows for
multiple read requests from multiple threads concurrently without
ever having to wait on the update of new structure values to
complete. All read operations will be safe if they don’t exceed
the cycle update time to complete.
Conclusion
Kahless_9
contains many other abstractions which also make coding easier and
more secure but cannot realistically mention all of them within this
blog. Even though the linux version of the framework does not
contain the database and the mfc libraries, it can still be used to
create very powerful applications, especially network based. Such
applications can be fully developed and tested on windows before
compiling and implementing on linux.