C++11中引入了多线程头文件<thread>
,让我们能更方便的使用多线程进行编程
void TestThread(int index)
{
std::cout << "Child Thread" << index << " id " << std::this_thread::get_id() << std::endl;
std::cout << "Child Thread" << index << "Stop" << std::endl;
}
int main()
{
std::thread newThread1(TestThread, 1);
std::thread newThread2(TestThread, 2);
std::cout << "Main Thread id " << std::this_thread::get_id() << std::endl;
std::cout << "Main Thread Stop" << std::endl;
if (newThread1.joinable())
newThread1.join();
if (newThread2.joinable())
newThread2.join();
}
众所周知,线程具有异步性,也就是说这道程序的推进的方向是不确定的,而事实也正是如此
有两句话说得好,汇总一下就是:join()
和detach()
总要调用一个,并且调用之前最好是要进行joinable()
检查
Never call join() or detach() on std::thread object with no associated executing thread
Never forget to call either join or detach on a std::thread object with associated executing thread
如果对一个子线程执行两次join()
操作,那么会抛出异常
void TestThread(int index)
{
std::cout << "Child Thread" << index << " id " << std::this_thread::get_id() << std::endl;
std::cout << "Child Thread" << index << "Stop" << std::endl;
}
int main()
{
std::thread newThread(TestThread, 1);
newThread.join();
newThread.join();
}
cppreference中提到,当joinable()
的线程被赋值或析构的时候,会调用std::terminate()
,而一般std::terminate()
意味着std::abort()
,也就是说如果对一个线程既不执行join()
操作也不执行detach()
操作,而它却被析构了,那么“it will cause the program to Terminate(终止)”
void TestThread(int index)
{
std::cout << "Child Thread" << index << " id " << std::this_thread::get_id() << std::endl;
std::cout << "Child Thread" << index << "Stop" << std::endl;
}
int main()
{
std::thread newThread(TestThread, 1);
newThread = std::thread(TestThread, 2);
std::cout << "Main Thread Sleep Begin" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "Main Thread Sleep End" << std::endl;
if (newThread.joinable())
newThread.join();
}
抛开Bug不谈,由于线程的异步性,谁也说不清是先输出Thread1还是输出Thread2
针对这个Bug,可以这么理解:线程1由对象newThread管理,当C++底层告知操作系统去创建一个新线程并给它分配一些任务后,却马上创建了一个线程2交给newThread,这样子产生了一个覆盖操作,导致线程1被析构,而它又是可结合的,所以程序被terminate
正确的做法是
int main()
{
std::thread newThread(TestThread, 1);
if (newThread.joinable())
newThread.join();
newThread = std::thread(TestThread, 2);
if (newThread.joinable())
newThread.join();
}
通过前文中的示范,我们了解到可以通过传入一个函数指针来给一个线程分配它的“任务”
除了函数指针外,还可以通过lambda表达式,std::function
,std::bind
,仿函数对象等来解决
struct Handle
{
void operator()(int data) { std::cout << data << std::endl; }
};
int main()
{
std::thread t(Handle(), 10);
if (t.joinable())
t.join();
}
newThread.join()
操作代表当前线程将阻塞,直到newThread
完成它的任务newThread.detach()
操作代表newThread
与当前线程分开(即当前线程结束销毁后并不会同时销毁newThread
),但是程序结束后newThread
仍会被终止(即使它还没运行完,也会被操作系统强行叫停,这也可能会导致资源没有正确的释放)class TestClass
{
public:
TestClass() { cout << "create" << endl; }
TestClass(const TestClass& t) { cout << "copy" << endl; }
void print(int num)
{
for (int i = 0; i < num; i++)
cout << i << ends;
cout << endl;
}
};
void Func2()
{
cout << "start" << endl;
TestClass t;
std::thread newThread(&TestClass::print, &t, 10);
// 让当前线程等待newThread完成
newThread.join();
cout << "end" << endl;
}
int main()
{
std::thread t(Func2);
if (t.joinable())
t.join();
return 0;
}
程序的执行结果为
void repeat1000(int index, int time)
{
for (int i = 0; i < time; i++)
cout << index;
}
void detach_test()
{
thread newThread(repeat1000, 1, 100000);
newThread.detach();
newThread = thread(repeat1000, 2, 1000);
newThread.join();
}
int main()
{
{
thread t(detach_test);
t.join();
}
cout << endl << "start wait" << endl;
this_thread::sleep_for(chrono::seconds(5));
return 0;
}
我认为这是一个很好的解释join()
和detach()
的例子,下面来分析一下
t
,给它分配了detach_test()
任务t
完成它的任务t
的分配,开始执行detach_test()
t
进程再创建子线程newThread
,并给他分配(repeat1000, 1, 100000)
任务newThread
的“同时”,t
线程将其和newThread
分离(repeat1000, 1, 100000)
仍然在运行,同时t
线程给newThread
赋了一个新线程,任务为(repeat1000, 2, 1000)
newThread
调用join()
操作,t
进程阻塞,等待newThread
的(repeat1000, 2, 1000)
工作完成newThread
的(repeat1000, 2, 1000)
完成,也意味着t
进程的任务完成,此时主线程不再受到阻塞t
线程离开作用域,遭到销毁。但此时(repeat1000, 1, 100000)
操作因为比较耗时所以仍然还在运行(repeat1000, 1, 100000)
仍然在运行(repeat1000, 1, 100000)
执行到第八万次的时候就突然被终止了)在一般情况下,为了避免detach
或join
使用不当造成的程序错误,可以创建一个线程类,使用析构函数执行线程的分离或合并(join
)
void newThreadCallback(int* p)
{
std::cout << "Inside Thread: " << *p << std::endl;
// 等一秒钟 让startNewThread()执行结束 使i的内存空间被回收
std::this_thread::sleep_for(std::chrono::seconds(1));
// 抛出异常
*p = 19;
}
void startNewThread()
{
int i = 10;
std::cout << "Inside Main Thread: " << i << std::endl;
std::thread t(newThreadCallback, &i);
t.detach();
std::cout << "Inside Main Thread: " << i << std::endl;
}
int main()
{
startNewThread();
// 等两秒钟 让所有线程和方法都执行完毕再结束程序
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
因为一般堆对象需要使用delete
来销毁,所以也无法确定别的线程访问到指针的时候,它所指的内存是否有效
使用std::ref
或者std::cref
,使用方法几乎和std::bind
中使一致的,所以不再赘述
操作系统应该学过,竞争就是多个线程同时访问一块内存区域,导致不可预估的结果
class Wallet
{
int money;
public:
Wallet() : money(0) {}
int getMoney() const { return money; }
void addMoney(int increase)
{
for (int i = 0; i < increase; ++i)
money++;
}
};
int testMultiThreadWallet()
{
Wallet walletObject;
std::vector<std::thread> threads;
// 创建五条线程异步访问Wallet "理应"得到的结果为5000
threads.reserve(5);
// reserve对应_back而resize对应[i]
for (int i = 0; i < 5; ++i)
threads.emplace_back(&Wallet::addMoney, &walletObject, 1000);
// 等所有线程执行完再结束
for (auto& thread : threads)
thread.join();
return walletObject.getMoney();
}
int main()
{
int val = 0;
for (int k = 0; k < 1000; k++)
{
if ((val = testMultiThreadWallet()) != 5000)
std::cout << "Error at count = " << k << " Money in Wallet = " << val << std::endl;
}
return 0;
}
某次程序执行的结果为,这种结果是不确定的,可能1000次实验,一次都不会出错,也可能出现多至十多次错误
这一行短短的代码其实发生了三件事
money++;
money
的值加载进寄存器中money
所在的内存中而当由于线程具有异步性,在不加锁的情况下我们无法控制多条线程对money
的访问顺序,那么就可能出现以下这种情况
操作系统中的PV操作与之类似,关键就是加锁
使用互斥锁解锁上文中的钱包问题
#include<mutex>
class Wallet
{
int money;
mutex mutexLock;
public:
Wallet() : money(0) {}
int getMoney() const { return money; }
void addMoney(int increase)
{
mutexLock.lock();
for (int i = 0; i < increase; ++i)
money++;
mutexLock.unlock();
}
};
但是如果一个线程在加锁后并没有解锁,那么所有其他线程将会一直等待,导致程序无法结束(当使用join
的情况下)
相反的,如果没上锁就解锁
因此可以在此基础上做一层简单的封装
class SmartMutex
{
private:
mutex mutexLock;
public:
void AutoLock(const function<void()>& func)
{
mutexLock.lock();
func();
mutexLock.unlock();
}
};
class Wallet
{
int money;
SmartMutex smartMutex;
public:
Wallet() : money(0) {}
int getMoney() const { return money; }
void addMoney(int increase)
{
smartMutex.AutoLock([this, &increase]()
{
for (int i = 0; i < increase; ++i)
this->money++;
});
}
};
或者可以使用std::lock_guard
std::lock_guard
有点像一个智能指针,在它的作用域结束后,会调用析构函数,完成对互斥锁的解锁操作
class Wallet
{
int money;
mutex mutexLock;
public:
Wallet() : money(0) {}
int getMoney() const { return money; }
void addMoney(int increase)
{
// 在lockGuard的构造函数中会自动上锁
lock_guard<mutex> lockGuard(mutexLock);
for (int i = 0; i < increase; ++i)
this->money++;
// 离开作用域 lockGuard析构函数调用 自动解锁
}
};
原文:https://www.cnblogs.com/tuapu/p/15242237.html