tinythread++库

引入

关于C++的跨平台多线程库,这里列举了一些知名的库:

  • Rogue Wave Libraries
  • Boost C++ Libraries
  • Intel Threading Building Blocks
  • Intel Cilk Plus
  • just::thread
  • OpenMP
  • OpenThreads
  • POCO Thread (part of POCO project - http://pocoproject.org/)
  • POSIX Threads
  • tinythread++
  • ZThreads
  • jlibcpp

其中,tinythread++是个很轻量级的,我们这里只介绍它

简介

TinyThread++只有一个cpp文件和两个h文件,使用时,将它们放在自己的工程中一道编译。

 TinyThread++的主页,可以下载这个库。下载解压,进入TinyThread++目录,主要有source和test两个子目录,前者包含TinyThread++的三个文件,后者包含三个简单的示例程序,其中test.cpp比较详细地列举了9种应用。

示例分析

下面是个使用detach的thread的小例子:

#include 
#include 
#include 

using namespace std;
using namespace tthread;

struct inputData
{
	int len;
	const char* data;
};

void ThreadProc(void *aArg)
{
	inputData input = *((inputData*)aArg);
	string s;
	s.assign(input.data, input.len);
	cout << "Sub thread id: " << this_thread::get_id() << " input data: " << s << endl;
}

int main(int argc, char* argv[])
{
	cout << "this thread id: " << this_thread::get_id() << endl;

	for(int i = 0; i < 3; ++i)
	{
		inputData input;
		string s;
		switch(i)
		{
		case 0:
			s = "#Data0#";
			break;
		case 1:
			s = "#Data1#";
			break;
		case 2:
			s = "#Data2#";
			break;
		}
		input.len = s.size();
		input.data = s.c_str();
		thread t(ThreadProc, &input);
		t.detach();
		//sleep(1);
	}

	cout << "..................." << endl;
	while(1)
		sleep(1);

	return 0;
}
其目的是,在主线程中创建3个从线程,分别传给它们不同的inputData输入参数,主要是输入的字符串内容不一样。希望在每个从线程中输出传入的字符串,然后自行退出。

实际输出的一种结果是:

this thread id: 1
Sub thread id: 2 input data: #Data1#
...................
Sub thread id: 3 input data: #Data2#
Sub thread id: 4 input data: #Data2#
按照我们期望的,2号线程,输出的input data部分应该是 #Data1#,3号,应该是#Data1#,实际得到的结果和设想的不一致。

调试发现,每次走到第16行的时候,指针aArg的值是一样的!即,22行申明的、44行传给ThreadProc函数的临时变量input,每次都在同样的内存空间中分配。于是,3个从线程,都从同一块内存区域读取输入参数值。这就造成,有可能前一个线程还没来得及从那段内存中读取数据,那段内存就在创建下一个从线程的时候被重新赋值(覆盖)了

对应上面的实际输出,覆盖操作就是:

2号线程还没来得及读,就被为3号线程而做的赋值操作给覆盖了,造成2号线程本该输出#Data0#的,却输出了#Data1#;

3号线程还没来得及读,就被为4号线程而做的赋值操作给覆盖了,造成3号线程本该输出#Data1#的,却输出了#Data2#。

事实上,如果我们放开46行的sleep操作,就不会出现上面的问题了,因为两个相邻的“创建从线程”的操作之间sleep了1秒钟,这段时间足以让前一个从线程在临时变量input未被覆盖的情况下从中读取它想要的数据!

问题的解决

输入给从线程的数据不使用栈上的临时变量,而是在堆上分配空间,每次都new出新的input变量(对应不同的内存空间),各个从线程从各自的堆空间中读取自己想要的数据,相互不干扰。从线程使用完毕堆上的数据后,自己将这些数据delete掉,然后自行退出。代码如下:

#include 
#include 
#include 
#include 

using namespace std;
using namespace tthread;

struct inputData
{
	int len;
	char* data;
};

void ThreadProc(void *aArg)
{
	inputData input = *((inputData*)aArg);
	string s;
	s.assign(input.data, input.len);
	cout << "Sub thread id: " << this_thread::get_id() << " input data: " << s << endl;
	delete [] input.data;
	delete aArg;
}

int main(int argc, char* argv[])
{
	cout << "this thread id: " << this_thread::get_id() << endl;

	for(int i = 0; i < 3; ++i)
	{
		inputData *input = new inputData;
		input->data = new char[20];
		input->len = strlen("#Data0");
		switch(i)
		{
		case 0:
			strcpy(input->data, "#Data0#");
			break;
		case 1:
			strcpy(input->data, "#Data1#");
			break;
		case 2:
			strcpy(input->data, "#Data2#");
			break;
		}
		thread t(ThreadProc, (void*)input);
		t.detach();
	}

	cout << "..................." << endl;
	while(1)
		sleep(1);

	return 0;
}

本示例改正后的工程在这里


关于线程detach

上面改正过的代码中,第46行,thread类型变量t其实是个临时变量,29行的for循环每次遍历都会创建一个新的变量t,并且每次遍历结束时,都会自动销毁t,这对创建的从线程有没有影响?我们看tinythread++的tinythread.cpp文件,~thread()析构函数是这样实现的:
thread::~thread()
{
  if(joinable())
    std::terminate();
}
如果是joinable的,就立即销毁该线程相关的资源;否则,什么也不做。

回到上面修改过的代码,第47行,有t.detach()操作,如果我们屏蔽它,会发现程序在第19行crash了。原因在于, 第19行的执行晚于~thread()的执行,即要读的那段内存上的对象以及销毁了,从线程再去从中读数据,于是crash了。事实上,如果我们把第47行的t.detach()改成sleep(1),会发现程序正常运行,那是因为1秒钟的sleep足够让从线程在~thread()执行前,从栈上复制它想要的数据,复制成功后,~thread()再执行对从线程没有影响。

以上说明了,栈上的thread类型临时变量t,会在出栈时被销毁,可能造成从线程异常的情况。但是,我们的代码中47行写了t.detach(),从而从线程不是joinable的,在~thread()中不会做资源销毁操作,从线程继续正常运行,直到线程函数执行完成后,从线程自行退出。

这也告诉我们, 只要主线程一直在运行,可以让从线程的thread对象是栈上的临时对象,即不用new thread对象t,也不用事后delete t,只要在申明t之后,执行t.detach()就可以了。但是,缺陷是,在主线程要退出时,不能t.join(),也就是主线程不会等待所有从线程都正常退出了才退出,不能做到“graceful exit”。


你可能感兴趣的:(tinythread++库)