在C++11之前,如果想要实现并发编程,就需要使用具体平台提供的并发编程相关的库,非常的不方便,而且还不支持跨平台。在C++11之后,终于在语言层面有了并发编程的支持。包括线程控制、互斥锁、条件变量、区域锁(lockguard)、原子操作、异步等。
C++11封装了一套线程库,位于thread文件中。
使用std::thread类创建线程,构造thread对象时传入一个可调用对象作为参数,如果有 参数,同时传入参数。构造完成后,新的线程被创建,同时执行该可调用对象。
std::thread默认构造函数构造的对象不关联任何线程,判断一个thread对象是否关联线程可以用joinable()接口。
joinable的对象析构前,要么用join()等待结束,要么用detach()接触与线程的关联。
thread类没有拷贝构造和拷贝赋值,可以move,没有两个thread对象会表示同一线程。
c++11创建线程、分离线程、回收线程、获取线程id
#include
#include >
using namespace std;
void ThreadFunc(int nThreadNum)
{
cout << "INFO: Num is " << nThreadNum << endl;
}
int main()
{
thread TestThread(ThreadFunc, 1);
thread TestThread_detach(ThreadFunc, 2);
TestThead_detach.detach();
cout << TestThread.get_id() << endl;
TestThread.join();
return 0;
}
#include
#include
#include
using namespace std;
void AddCount(mutex &mutexTest, int &nCount)
{
for (int i = 0; i < 1000; ++i)
{
mutexTest.lock();
++nCount;
mutexTest.unlock();
}
}
int main()
{
thread arrThreads[5];
mutex mutexTest;
int nCount = 0;
for (auto& threadIte : arrThreads)
{
threadIte = thread(AddCount, ref(mutexTest), ref(nCount));
}
for (thread& threadIte : arrThreads)
{
threadIte.join();
}
cout << nCount << endl;
return 0;
}
有作用域的mutex ,让 程序更稳定,防止死锁
类模板std::lock_guard是mutex封装器,通过便利的RAII机制在其作用域内占有mutex。
创建lock_guard对象时,它试图接收给定mutex的所有权。当程序流程离开创建lock_guard对象的作用域时,lock_guard对象被自动销毁并释放mutex,lock_guard类也是不可复制的。
一般,需要加锁的代码段,我们用{}括起来形成一个作用域,括号的开端创建lock_guard对象,把mutex对象作为参数传入lock_guard的构造函数即可,比如上面的例子加锁的部分,我们可以改写如下
std::thread t([&](){
for(int i=0;i<10000;i++)
std::lock_guard<mutex> guard(mtx);
++num;
}
})
进入作用域,临时对象guard创建,获取mutex控制权(构造函数里调用了mutex的lock接口),离开作用域,临时对象guard销毁,释放了mutex(析构函数里调用了unlock接口)
条件不满足,线程被阻塞。一个线程等待某一变量的成立而阻塞,另一线程使该变量变化而使上一线程成立,同时发送信号(notify)唤醒wait的线程。
#include
#include
#include
#include
#include
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// 等待直至 main() 发送数据
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return ready;});
// 等待后,我们占有锁。
std::cout << "Worker thread is processing data\n";
data += " after processing";
// 发送数据回 main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// 通知前完成手动解锁,以避免等待线程才被唤醒就阻塞(细节见 notify_one )
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// 发送数据到 worker 线程
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// 等候 worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
C++11起提供了atomic,可以使用它定义一个原子类型。
解决该问题的话,加锁,或者提供一种定义原子类型的方法。
#include
#include
int main(){
std::atomic<int64_t> value;//定义一个原子对象
int64_t x=10;
value.store(x);//将x存入该原子对象中
value++;//使用原子对象特化的自增
int64_t y=value.load(std::memory_order_relaxed);//取出原子对象中的值
std::cout<<y<<std::endl;
}
特化成员函数 | 说明 |
---|---|
fetch_add | 原子地将参数加到存储于原子对象的值,并返回先前保有的值 |
fetch_sub | 原子地进行参数和原子对象的值的逐位与,并获得先前保有的值 |
fetch_or | 原子地进行参数和原子对象的值的逐位或,并获得先前保有的值 |
fetch_xor | 原子地进行参数和原子对象的值的逐位异或,并获得先前保有的值 |
operator++ | 令原子值增加一 |
operator++(int) | 令原子值增加一 |
operator– | 令原子值减少一 |
operator–(int) | 令原子值减少一 |
async 异步可以让耗时的操作不影响当前主进程的执行,而是单独的启动一个新的线程来运行任务.
std::async执行的任务可以运行在单独线程里,也可以不运行在单独线程里,如果是立即执行则会开一个线程去执行,如果是延后执行,则会在调用任务的线程里阻塞执行。
立即执行,开一个线程去执行
如果是立即执行,那么还需要注意future对象的析构问题。
如果要析构std::async返回的future对象,那么future对象的析构函数会阻塞当前线程,直到任务执行结束
#include
#include
bool myfunc(int data1, int data2)
{
std::cout << "myfunc id: " << std::this_thread::get_id() << "\n";
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::cout << "main id: " << std::this_thread::get_id() << "\n";
std::future<bool> fu = std::async(myfunc, 100, 200); // 100和200是myfunc需要的参数
// 阻塞等待
fu.get();
return 0;
}
#include
#include
bool myfunc(int data1, int data2)
{
std::cout << "myfunc id: " << std::this_thread::get_id() << "\n";
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::cout << "main id: " << std::this_thread::get_id() << "\n";
std::future<bool> fu = std::async(std::launch::deferred, myfunc, 100, 200); // 100和200是myfunc需要的参数
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
// 阻塞等待
fu.get();
return 0;
}
#include
#include
bool myfunc(int data1, int data2)
{
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::packaged_task<bool(int, int)> task(myfunc);
std::future<bool> fu = task.get_future();
// 必须要使用std::move把task转成右值
std::thread t(std::move(task), 100, 200); // 100和200是myfunc需要的参数
// 调用get进行阻塞等待
std::cout << fu.get() << "\n";
t.join();
return 0;
}
std::promise,相比于前2个,std::promise不需要等到任务结束就可以拿到future对象里的值,而且自由度最高,可以根据需要来决定返回值类型,不必是任务结束时的返回值。另外,std::promise只是个异步结果的提供者,不像前2者可以传递任务进来。
在主线程中初始化一个可以传递string的promise,获取他的future,开启一个新线程,把promise传进去,这样就可以在这个线程函数中给promise放置信息,在主线程中,可以不用等这个线程函数结束就用future的get获取它放置的信息。另外可以将promise的类型设为void,只起到通知作用。
#include
#include
#include
bool myfunc(std::promise<std::string> pro, int data1, int data2)
{
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
pro.set_value("hello");
// 等待2秒
std::this_thread::sleep_for(std::chrono::seconds(2));
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::promise<std::string> pro;
std::future<std::string> fu = pro.get_future();
// 必须使用std::move把pro转成右值,之后pro就不能再被使用
std::thread t(myfunc, std::move(pro), 100, 200);
std::cout << fu.get() << "\n";
t.join();
return 0;
}
#include
#include
#include
bool myfunc(std::future<void> fu, int data1, int data2)
{
fu.get();
std::cout << "DDD\n";
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::promise<void> pro;
std::future<void> fu = pro.get_future();
std::thread t(myfunc, std::move(fu), 100, 200);
// 等待1s
std::this_thread::sleep_for(std::chrono::seconds(1));
// 进行通知
pro.set_value();
t.join();
return 0;
}
C++异步操作三种方式的区别
C++11中的异步操作
C++多线程编程
日暮乡关走歧途,朝朝暮暮滂沱路
寒山远方望金台,伶仃之余谈风生