线程创建
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;
}
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;
}
会只输出一个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;
}
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;
}
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;
}
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;
}
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;
}
数据竞争和互斥 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参数个数不固定
使用尽量少的锁
在使用了一个锁之后,调用其他不熟悉的函数需要小心,因为其他函数可能包含其他的锁