.

  • 线程创建
  • 概念
  • 并发编程
  • 进程之间相互通讯的方法
  • 多线程的优缺点
  • 运行环境
  • 实操
  • 1.HelloWord
  • 2.使用detach,主线程将不等子线程运行完就结束
  • 3.一个线程先使用了detach之后就不能join否则会报错。所以join之前先进行判断
  • 4.异常处理
  • 5.通过类创建线程
  • 6.通过仿函数创建
  • 7.添加参数进行构造
  • 8.通过引用传递参数std::ref
  • 9.不通过引用,(可能导致数据竞争),通过move创建
  • 10. hardware_concurrency输出可以并发编程的线程数量
  • 数据竞争和互斥 LogFile
  • 代码演示
  • 1.lock和unlock
  • 2.lock_guard
  • 3.使用LogFile类保护ofstream
  • 4.unique_lock可以锁住想要的部分
  • 5.unique_lock与lock_guard的比较
  • 6.f.open只打开一次
  • 使用call_once可以确保被一个线程调用一次
  • 死锁
  • 代码演示
  • 1.拥有一些资源,同时请求一些资源
  • 2.std::lock控制锁的顺序

线程创建

URL:https://www.bilibili.com/video/av39161756?p=1

概念

并发编程

包括多进程编程和多线程编程

进程之间相互通讯的方法

文件
管道
消息队列

多线程的优缺点

优点:
线程启动速度快
轻量级
开销低

缺点:
管理较难
不能在分布式系统下运行

运行环境

VS2013  + 控制台应用程序

实操

1.HelloWord

#include <iostream>
#include <thread>
#include <future>
using namespace std;

void helloworld()
{
	cout << "hello world \n";
}

int main()
{

	//开启一个线程 
	thread t(helloworld);
	cout << "hello world main thread\n";

	t.join();//主线程会等待t结束运行

	system("pause");
	return 0;
}

【C++ 11多线程】 快速入门:线程创建&数据竞争和互斥&LogFile&死锁_并发编程


2.使用detach,主线程将不等子线程运行完就结束

#include <iostream>
#include <thread>
#include <future>
using namespace std;

void helloworld()
{
	cout << "hello world" << endl;
}

int main()
{

	//开启一个线程 
	thread t(helloworld);
	t.detach();
	return 0;
}

【C++ 11多线程】 快速入门:线程创建&数据竞争和互斥&LogFile&死锁_抛出异常_02

会只输出一个h就结束了


3.一个线程先使用了detach之后就不能join否则会报错。所以join之前先进行判断

#include <iostream>
#include <thread>
#include <future>
using namespace std;

void helloworld()
{
	cout << "hello world" << endl;
}

int main()
{

	//开启一个线程 
	thread t(helloworld);
	t.detach();

	if (t.joinable()) {
		t.join();
	}
	return 0;
}

【C++ 11多线程】 快速入门:线程创建&数据竞争和互斥&LogFile&死锁_#include_03


4.异常处理

#include <iostream>
#include <thread>
#include <future>
using namespace std;

void helloworld()
{
	cout << "hello world" << endl;
}

int main()
{

	thread t(helloworld);
	
	
	for (int i = 0; i < 10; i++) {
		cout << "this is main thread" << endl;
	}
	//如果上面的代码抛出异常,t在join之前就会被销毁,这个时候代码就报错了
	t.join();
	
	return 0;
}

修改为

#include <iostream>
#include <thread>
#include <future>
using namespace std;

void helloworld()
{
	cout << "hello world" << endl;
}

int main()
{

	thread t(helloworld);
	try {
	
		for (int i = 0; i < 10; i++) {
			cout << "this is main thread" << endl;
		}

	}
	catch (...) {
		t.join();
		throw;
	}
	t.join();
	
	return 0;
}

不管主线程是否抛出异常,t都可以正常join


5.通过类创建线程

#include <iostream>
#include <thread>
#include <future>
using namespace std;

void helloworld()
{
	cout << "hello world" << endl;
}

class Fctor {
public:
	void operator()() {
		for (int i = 0; i > -10; i--) {
			cout << "from t1: " << i << endl;
		}
	}

};
int main()
{
	Fctor fct;
	thread t1(fct);
	
	
	for (int i = 0; i < 10; i++) {
		cout << "from main: " << i << endl;
	}
	//如果上面的代码抛出异常,t在join之前就会被销毁,这个时候代码就报错了
	t1.join();
	
	return 0;
}

【C++ 11多线程】 快速入门:线程创建&数据竞争和互斥&LogFile&死锁_#include_04


6.通过仿函数创建

#include <iostream>
#include <thread>
#include <future>
using namespace std;

class Fctor {
public:
	void operator()() {
		for (int i = 0; i > -10; i--) {
			cout << "from t1: " << i << endl;
		}
	}

};
int main()
{
	Fctor fct;
	//thread t1(fct);
	//也可以
	thread t1((Fctor()));
	
	for (int i = 0; i < 10; i++) {
		cout << "from main: " << i << endl;
	}
	//如果上面的代码抛出异常,t在join之前就会被销毁,这个时候代码就报错了
	t1.join();
	
	return 0;
}

7.添加参数进行构造

#include <iostream>
#include <thread>
#include <future>
#include <string>
using namespace std;

class Fctor {
public:
	void operator()(std::string msg) {
		for (int i = 0; i > -10; i--) {
			cout << "from t1: " << msg << endl;
		}
	}

};
int main()
{
	Fctor fct;
	string s = "hello world";
	thread t1((Fctor()),s);
	
	for (int i = 0; i < 10; i++) {
		cout << "from main: " << i << endl;
	}
	//如果上面的代码抛出异常,t在join之前就会被销毁,这个时候代码就报错了
	t1.join();
	
	return 0;
}

8.通过引用传递参数std::ref

#include <iostream>
#include <thread>
#include <future>
#include <string>
using namespace std;

class Fctor {
public:
	void operator()(std::string& msg) {
		
		cout << "from t1: " << msg << endl;
		msg = "xiugai";
	}

};
int main()
{
	Fctor fct;
	string s = "hello world";
	thread t1((Fctor()),std::ref(s));
	t1.join();
	cout << "from main: " << s << endl;

	return 0;
}

【C++ 11多线程】 快速入门:线程创建&数据竞争和互斥&LogFile&死锁_并发编程_05


9.不通过引用,(可能导致数据竞争),通过move创建

#include <iostream>
#include <thread>
#include <future>
#include <string>
using namespace std;

class Fctor {
public:
	void operator()(std::string msg) {
		
		cout << "from t1: " << msg << endl;
		msg = "xiugai";
	}

};
int main()
{
	Fctor fct;
	string s = "hello world";
	
	cout << this_thread::get_id() << endl;

	thread t1((Fctor()),std::move(s));
	thread t2 = std::move(t1);

	t2.join();
	cout << "from main: " << s << endl;
	cout << "from main: " << t2.get_id()<< endl;

	return 0;
}

【C++ 11多线程】 快速入门:线程创建&数据竞争和互斥&LogFile&死锁_并发编程_06


move把s移走了


10. hardware_concurrency输出可以并发编程的线程数量

#include <iostream>
#include <thread>
#include <future>
#include <string>
using namespace std;

class Fctor {
public:
	void operator()(std::string msg) {
		
		cout << "from t1: " << msg << endl;
		msg = "xiugai";
	}

};
int main()
{
	Fctor fct;
	string s = "hello world";
	
	cout << this_thread::get_id() << endl;

	thread t1((Fctor()),std::move(s));
	t1.join();

	cout<<std::thread::hardware_concurrency()<<endl;

	return 0;
}

【C++ 11多线程】 快速入门:线程创建&数据竞争和互斥&LogFile&死锁_抛出异常_07

数据竞争和互斥 LogFile

https://www.bilibili.com/video/av39161756?p=3

代码演示

1.lock和unlock

#include <iostream>
#include <thread>
#include <future>
#include <mutex>
#include <string>

std::mutex mu;

using namespace std;


void share_print(string msg,int id)
{
	mu.lock();//加锁
	cout << msg<< id << endl;
	//如果cout抛出异常,那么mu将会被永远锁住
	mu.unlock();//解锁
}


void fountion1() {
	for (int i = 0; i > -100; i--) {
		share_print( "from t1: ", i);
	}
}


int main()
{
	thread t1(fountion1);

	for (int i = 0; i < 100; i++) {
		share_print("from main: ",i);
	}
	//如果上面的代码抛出异常,t在join之前就会被销毁,这个时候代码就报错了
	t1.join();

	return 0;
}

2.lock_guard

void share_print(string msg,int id)
{
	lock_guard<mutex> guard(mu);

	cout << msg<< id << endl;
	//在guard析构的时候,mu就会被释放

}

3.使用LogFile类保护ofstream

#include <iostream>
#include <thread>
#include <future>
#include <mutex>
#include <string>
#include <fstream>

std::mutex mu;

using namespace std;

class LofFile {
public:
	LofFile() {
		f.open("log.txt");
	}
	
	void share_print(string id, int val) {
		lock_guard<mutex> locker(m_mutex);
		f << "from " << id << ":" << val << endl;
	}
protected:
private:
	mutex m_mutex;
	ofstream f;
	/**************
	|*这样的话 f 就在LofFile类的保护下了,
	|*不使用这个这个类,就无法使用 f
	|*不要写成员函数返回 f 的引用,如果这样写就把f暴露在外了
	|*
	|* 除此之外,下面这样写也是不安全的
	|* void pocessf(void fun(ofstream &) ){
	|*	  fun(f);
	|* }
	|*
	*/
};

void fountion1(LofFile & log) {
	for (int i = 0; i > -100; i--) {
		log.share_print( "from t1: ", i);
	}
}

int main()
{
	LofFile log;
	thread t1(fountion1,ref(log));
	for (int i = 0; i < 100; i++) {
		log.share_print("from main: ",i);
	}
	//如果上面的代码抛出异常,t在join之前就会被销毁,这个时候代码就报错了
	t1.join();
	return 0;
}

4.unique_lock可以锁住想要的部分

class LofFile {
public:
	LofFile() {
		f.open("log.txt");
	}

	void share_print(string id, int val) {
		std::unique_lock<std::mutex> locker(m_mutex);
		cout << "from " << id << ":" << val << endl;
		locker.unlock();
		//...这里是不需要被锁着的代码
	}
	void share_print2(string id, int val) {
		std::lock(m_mutex, m_mutex2);
		lock_guard<mutex> locker2(m_mutex2,std::adopt_lock);
		lock_guard<mutex> locker(m_mutex, std::adopt_lock);
		cout << "from " << id << ":" << val << endl;
	}
protected:
private:
	mutex m_mutex;
	mutex m_mutex2;
	ofstream f;
};

5.unique_lock与lock_guard的比较

class LofFile {
public:
	LofFile() {
		f.open("log.txt");
	}

	void share_print(string id, int val) {
		std::unique_lock<std::mutex> locker(m_mutex,std::defer_lock);
		//加上std::defer_lock参数...这里是不需要被锁着的代码
		locker.lock();
		cout << "from " << id << ":" << val << endl;
		locker.unlock();
		//...这里是不需要被锁着的代码

		//也可以字啊解锁之后,再次加锁
		locker.lock();
		//...这里是需要锁着的code

		//unique_lock 可以被移动,不可以被复制
		//lock_guard 不可以被移动,不可以被复制

		std::unique_lock<mutex> locker2 = std::move(locker);

		//unique这个弹性的操作有开销,
		//一般使用lock_guard能满足要求首先选择lock_guard
	}
protected:
private:
	mutex m_mutex;
	mutex m_mutex2;
	ofstream f;
};

6.f.open只打开一次

class LofFile {
public:
	LofFile() {
		f.open("log.txt");
	}

	void share_print(string id, int val) {
		//每次打印的时候都要进行判断,消耗了资源
		{
			std::unique_lock<std::mutex> locker2(m_mutex_open, std::defer_lock);
			if (!f.is_open()) {
				f.open("log.txt");
			}
		}
		std::unique_lock<std::mutex> locker(m_mutex,std::defer_lock);
		//加上std::defer_lock参数...这里是不需要被锁着的代码
		locker.lock();
		cout << "from " << id << ":" << val << endl;
		locker.unlock();
	}
protected:
private:
	mutex m_mutex;
	mutex m_mutex_open;
	//std::once_flag m_flag;
	ofstream f;
};

使用call_once可以确保被一个线程调用一次

class LofFile {
public:
	LofFile() {
		f.open("log.txt");
	}

	void share_print(string id, int val) {
		/*{
			std::unique_lock<std::mutex> locker2(m_mutex_open, std::defer_lock);
			if (!f.is_open()) {
				f.open("log.txt");
			}
		}*/
		//可以确保这个代码只被一个线程调用一次,比上面的代码更加高效
		std::call_once(m_flag, [&]() {f.open("log.txt"); });
		std::unique_lock<std::mutex> locker(m_mutex,std::defer_lock);
		//加上std::defer_lock参数...这里是不需要被锁着的代码
		locker.lock();
		cout << "from " << id << ":" << val << endl;
		locker.unlock();
	}
protected:
private:
	mutex m_mutex;
	//mutex m_mutex_open;
	std::once_flag m_flag;
	ofstream f;
};

死锁

https://www.bilibili.com/video/av39161756?p=4

代码演示

1.拥有一些资源,同时请求一些资源

#include <iostream>
#include <thread>
#include <future>
#include <mutex>
#include <string>
#include <fstream>


using namespace std;

class LofFile {
public:
	LofFile() {
		f.open("log.txt");
	}

	void share_print(string id, int val) {
		lock_guard<mutex> locker(m_mutex);
		lock_guard<mutex> locker2(m_mutex2);
		cout << "from " << id << ":" << val << endl;
	}
	void share_print2(string id, int val) {
		lock_guard<mutex> locker2(m_mutex2);
		lock_guard<mutex> locker(m_mutex);
		cout << "from " << id << ":" << val << endl;
	}
protected:
private:
	mutex m_mutex;
	mutex m_mutex2;
	ofstream f;
};

void fountion1(LofFile & log) {
	for (int i = 0; i > -1000; i--) {
		log.share_print( "from t1: ", i);
	}
}


int main()
{
	LofFile log;
	thread t1(fountion1,ref(log));

	for (int i = 0; i < 1000; i++) {
		log.share_print2("from main: ",i);
	}
	//如果上面的代码抛出异常,t在join之前就会被销毁,这个时候代码就报错了
	t1.join();

	return 0;
}

2.std::lock控制锁的顺序

#include <iostream>
#include <thread>
#include <future>
#include <mutex>
#include <string>
#include <fstream>



using namespace std;

class LofFile {
public:
	LofFile() {
		f.open("log.txt");
	}

	void share_print(string id, int val) {
		std::lock(m_mutex, m_mutex2);
		lock_guard<mutex> locker(m_mutex,std::adopt_lock);
		lock_guard<mutex> locker2(m_mutex2, std::adopt_lock);
		cout << "from " << id << ":" << val << endl;
	}
	void share_print2(string id, int val) {
		std::lock(m_mutex, m_mutex2);
		lock_guard<mutex> locker2(m_mutex2,std::adopt_lock);
		lock_guard<mutex> locker(m_mutex, std::adopt_lock);
		cout << "from " << id << ":" << val << endl;
	}
protected:
private:
	mutex m_mutex;
	mutex m_mutex2;
	ofstream f;
};

void fountion1(LofFile & log) {
	for (int i = 0; i > -100; i--) {
		log.share_print( "from t1: ", i);
	}
}


int main()
{
	LofFile log;
	thread t1(fountion1,ref(log));

	for (int i = 0; i < 100; i++) {
		log.share_print2("from main: ",i);
	}
	//如果上面的代码抛出异常,t在join之前就会被销毁,这个时候代码就报错了
	t1.join();

	return 0;
}

用std::lock给锁加一个顺序,就可以避免死锁
lock参数个数不固定


使用尽量少的锁
在使用了一个锁之后,调用其他不熟悉的函数需要小心,因为其他函数可能包含其他的锁