C++11多线程编程Part.2

本文深入探讨C++11的多线程编程,讲解了std::thread的joinable()重要性,避免主线程提前结束导致的错误。同时介绍了Lambda表达式的使用,包括参数传递方式和作用。最后触及多线程同步问题,强调了原子操作在并发编程中的必要性。

std::thread Value&Value.joinable()总是成对出现

他们的关系就像是构造函数和析构函数一样,如果创建一个线程而不去joinable这个线程,很有可能造成MainThread已经结束但是this_thread并没有结束,导致this_thread(workThread)无法正常完成工作或者无法正常退出,引发错误。
joinable的作用是询问当前线程是否已经运行完毕,如果没有运行完毕,MainThread则会在这一行继续等待,这里的等待不是while(true)这样低效的等待,而是将MainThread挂起,释放CPU资源,不会浪费资源,需要注意的是,此时MainThread是挂起状态,代码不会继续向下运行,需要加joinable的地方应该是我们预计这个WorkThread即将完成任务时或者MainThread要结束的时候添加。一是保证MainThread不会在应该运行时挂起,二是保证WorkThread能够正常退出。
与joinable相关的还有个join方法,实际上join方法才是真正的等

使用Lmabda表达式(匿名函数)来为工作线程传入参数

Lmabda表达式是一种匿名函数,很适合某个方法只运行一次或者操作一次时使用,这样我们不必再额外添加一个方法或者在已经封装好的类中添加成员方法,这都不符合封装开放原则。Lmabda表达式就很合适解决这类问题。
Lmabda表达式的结构如下:

//Lmabda Function as same as inline Function
	//lambda Function def No name,It is useful to Quickly code a algorithm
	//auto RealWorks = [&counter2, &TotalValue_2](auto IterStart, auto IterEnd) {
	//	for (; IterStart != IterEnd; ++IterStart) {
	//		TotalValue_2 += Count_Up(*IterStart);
	//		counter2.Add_Value();
	//	}
	//};
auto localvalue = [函数对象参数](操作符重载函数参数)mutable 或 exception 声明 -> 返回值类型{函数体};

[函数对象参数]

标识Lmabda表达式的开始,这部分是基础,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义 Lambda 为止时 Lambda 所在作用范围内可见的局部变量(包括 Lambda 所在类的 this)。
也就是说如果要在Lmabda表达式中使用局部变量的时候,需要在[]中将其显式的写出,这样编译器才会去寻找这些局部变量,并将其加载到Lmabda表达式中。
下面给出函数对象参数的形式:

  1. [] 不传入任何对象参数

  2. [=] 函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)

  3. [&] 函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。

  4. [this] 函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。

  5. [Value] 将 Value 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要修改传递进来的拷贝,可以添加 mutable 修饰符。

  6. [&Value] 将 Value 按引用进行传递。

  7. [A,&B] 将A按值传递,B按引用进行传递。

  8. [=,&A,&B] 除A和B按引用进行传递外,其他参数都按值进行传递。

  9. [&,A,B] 除A和B按值进行传递外,其他参数都按引用进行传递。.

(操作符重载函数参数)

标识重载的 () 操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递。

mutable 或 exception 声明

这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。

-> 返回值类型

标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。

{函数体}

标识函数的实现,这部分不能省略,但函数体可以为空。

例子

void ShowIt(int X, int Y, int Z) { 
	std::cout << X << " " << Y << " " << Z << " " << std::endl; 
}
void Plus(int X, int Y, int& Z) { Z += X + Y; }
void PrintString(const std::string& str1, const std::string& str2) {
	std::cout << "Hello " << str1 << " " << str2 << std::endl;
}

void TestPart4(){
	int X = 2;
	int Y = 2;
	int Z = 3;
	std::thread th1([=](){ShowIt(X, Y, Z); });
	th1.join();
	std::thread th2(ShowIt, X, Y, Z);//only in the Value Pass way;
	//std::thread th2(Plus, X, Y, Z)//error!
	th2.join();
	std::thread th3([=, &Z]() {Plus(X, Y, Z); });
	th3.join();
	std::thread th4(Plus,X,Y,std::ref(Z));//ref is a class
	th4.join();

	std::string str1("ABC");
	std::string str2("DEF");
	std::thread th5([&](){PrintString(str1, str2); });
	th5.join();
	std::thread th6(PrintString,str1,str2);
	th6.join();
	std::thread th7(PrintString, std::cref(str1), std::cref(str2));
	th7.join();
}

多线程的同步问题初探

多线程比起单线程有着速度和效率上的优势,但是如果多个线程共享同一份数据,并进行操作,结果是不可预见的,同步就是多线程编程的一大难点,最经典的银行取钱问题,要保证每一步都是原子操作,并且有些本来是两步以上的动作也应该组合到一步完成,这里我们需要用到<atomic>头文件,保证每一步都是原子操作,结合Mutex来将多个步骤组合成一个原子操作。

//include <atomic> //Change the "int" to be a atomic
//at the Multithread coding,if we should't share the data in workthread,
//then we need to cut down the data in different thread.


//Change the "int" to be a atomic

template<typename Type>
class Counter
{
public:
	Type Get_Type_Value()const { return Sum; }
	void Add_Value() { Sum++; }
	void Add_Resource(Type Value) { TotalResource++; }
	int AveResource() {
		if (Sum == 0)return 1;
		return TotalResource / Sum;
	}
	Counter() :Sum{ Type() },TotalResource {Type()}{}
private:
	std::atomic<Type> Sum;//Change the "int" to be a atomic
	std::atomic<Type> TotalResource;//but atomic can't make the two sentences like a atomic
};

template<typename Type>
Type Count_Up(Type Value)
{
	//count up the value
	return Value + Value;
}

template<typename Iter>
void RealWorks(Counter<int>& counter2, int64_t& TotalValue_2, Iter IterStart, Iter IterEnd) {
	for (; IterStart != IterEnd; ++IterStart) {
		TotalValue_2 += Count_Up(*IterStart);
		counter2.Add_Value();
		counter2.Add_Resource(1);
	}
};

bool Testthread(Counter<int>& counter, int Max_Count) {
	auto count = counter.Get_Type_Value();
	auto Ave = counter.AveResource();
	if (Ave != 1) { std::cout << "There are something error happed\n"; };
	if (count == Max_Count)return true;
	else return false;
}

int main()
{
	//TestPart4();
	std::vector<int> Box;
	//Attention:some times,MultiThread maybe count up wrong but Single Thread would count up right.
	//Example:
	for (unsigned int i = 0; i < 10000000; i++) { Box.push_back(rand() % 100); }
	Counter<int> counter;
	int Size = Box.size();
	auto Iter = Box.begin() + (Size / 3);
	auto Iter_Mid = Box.begin() + (Size / 3 * 2);
	auto Iter_End = Box.end();
	int64_t TotalValue_2 = int64_t();
	std::thread th1([=, &counter,&TotalValue_2] {RealWorks(counter, TotalValue_2, Iter, Iter_Mid); });
	std::thread printThread([&counter, &TotalValue_2] {
		while (!Testthread(counter, 10000000));
		});
	std::thread th2([=, &counter, &TotalValue_2] {RealWorks(counter, TotalValue_2, Iter_Mid,Iter_End); });
	RealWorks(counter, TotalValue_2, Box.begin(), Iter);
	th1.join();
	th2.join();
	printThread.join();//dead loop
	return 0;
}

例子中使用了printThread这个线程来检测是否数据自加正确,发现如果不使用atomic一定是不正确的,因为++操作本身不是一个原子操作,给int设置为atomic类型。std::atomic<int> Sum;这样在使用int类型的Sum时,相当于在调用一个函数。关于Mutex的相关内容,在Part.3介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值