一、线程thread
使用std::thread创建线程,提供线程入口函数或者函数对象,同时还可以指定线程函数的参数。
1. 使用举例
1.1 线程入口函数是普通函数
#include <iostream> #include <thread> // 需要包含头文件 using namespace std; // 1. 不传值 void func1() { cout << "func1 into" << endl; } // 2. 传入两个参数 void func2(int a, int b) { cout << "func2 a + b = " << a+b << endl; } // 3. 传引用 void func3(int &val) { cout << "func3 c = " << &val << endl; val += 20; } // 4. detach 测试 void func4() { cout << "func4 into sleep " << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); cout << "func4 leave " << endl; } // 5. move void func5() { cout << "func5 into sleep " << endl; std::this_thread::sleep_for(std::chrono::seconds(1)); cout << "func5 leave " << endl; } int main() { cout << "---------- main1 ----------------------" << endl; std::thread t1(func1); t1.join(); // join阻塞等待线程返回,线程若是不结束,会阻塞在这行代码 cout << "\n---------- main2 ----------------------" << endl; int a = 5, b = 6; std::thread t2(func2, a, b); t2.join(); cout << "\n---------- main3 ----------------------" << endl; int c = 10; std::thread t3(func3, std::ref(c)); t3.join(); cout << "main3 c = " << &c << ", "<<c << endl; cout << "\n---------- main4 ----------------------"<<c << endl; std::thread t4(func4); t4.detach(); // detach 设置分离属性,设置后t4线程独立运行,待主线程退出后回收相关资源 std::this_thread::sleep_for(std::chrono::seconds(2)); // 如果不休眠,主线程会直接退出,导致线程4来不及执行就退出 // 5 .move cout << "\n\n -------- main5--------------------------\n"; thread t5_1(func5); thread t5_2(std::move(t5_1)); // t5_1 线程失去所有权 // t5_1.join(); // t5_1 线程失去所有权,再去join 抛出异常 程序异常退出 t5_2.join(); return 0; }
运行结果:
1.2 线程入口函数是类的成员函数
#include <iostream> #include <thread> // 需要包含头文件 using namespace std; class A { public: // 4. 传入类函数 void func4(int a) { // std::this_thread::sleep_for(std::chrono::seconds(1)); cout << "thread:" << name_<< ", fun4 a = " << a << endl; } void setName(string name) { name_ = name; } void displayName() { cout << "this:" << this << ", name:" << name_ << endl; } void play() { std::cout<<"play call!"<<std::endl; } private: string name_; }; int main() { cout << "---------- main1 ----------------------"<< endl; // A *aptr = new A(); // aptr->setName("lwang"); // std::thread t1(&A::func4, aptr, 20); A a; a.setName("lwang"); std::thread t1(&A::func4, a, 20); t1.join(); return 0; }
运行结果:
二、互斥量mutex
2.1 作用
互斥量是多线程间访问某一共享变量时,保证变量可以被安全访问。mutex又称互斥量,是独占的互斥量。C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在 mutex 头文件中,所以如果你需要使用 std::mutex,就必须包含 mutex 头文件。
2.2 使用举例
#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex volatile int counter(0); // non-atomic counter std::mutex mtx; // locks access to counter void increases_10k_lock() { for (int i=0; i<10000; ++i) { mtx.lock(); ++counter; mtx.unlock(); } } void increases_10k_try_lock() { for (int i=0; i<10000; ++i) { if (mtx.try_lock()) { // only increase if currently not locked: // ++counter; // mtx.unlock(); // } } } int main() { std::thread threads[10]; for (int i=0; i<10; ++i) threads[i] = std::thread(increases_10k_try_lock); for (auto& th : threads) th.join(); std::cout << "try_lock counter: " << counter << std::endl; counter = 0; std::thread threadId[10]; for (int i=0; i<10; ++i) threadId[i] = std::thread(increases_10k_lock); for (auto& th : threadId) th.join(); std::cout << "lock counter: " << counter << std::endl; return 0; }
运行结果:
分析:try_lock() 如果在尝试获取锁的时候拿不到锁,就不能对counter加1,导致结果异常。使用 lock() 可以实现让counter正常加到10w。
三、条件变量
互斥量可以保证共享变量在多线程访问的安全性,但是无法实现线程的同步。线程同步指的我们希望线程按照我们预期的顺序执行的行为。c++11提供了对该行为的支持,就是条件变量。需要包含头文件:condition_variable
3.1 代码举例
使用条件变量实现一个同步队列,同步队列作为一个线程安全的数据共享区,经常用于线程之间数据读取。
四、原子变量
#include <iostream> #include <atomic> #include <thread> // std::atomic<int> count = 0;//错误初始化 std::atomic<int> count(0); // 准确初始化 void set_count(int x) { std::cout << "set_count:" << x << std::endl; count.store(x, std::memory_order_relaxed); // set value atomically } void print_count() { int x; do { x = count.load(std::memory_order_relaxed); // get value atomically } while (x==0); std::cout << "count: " << x << '\n'; } int main () { std::thread t1 (print_count); std::thread t2 (set_count, 10); t1.join(); t2.join(); std::cout << "main finish\n"; return 0; }
运行结果:
五、lock_guard和unique_lock
5.1 lock_guard
为了防止忘记解锁,C++11引入了一个叫做 std::lock_guard 的类模板。std::lock_guard 可以直接取代 lock() 和 unlock(),也就说使用 std::lock_guard 后,就不能再使用 lock() 和 unlock() 了。
std::lock_guard 在构造函数中加锁,在析构函数中解锁。
#include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::lock_guard #include <stdexcept> // std::logic_error std::mutex mtx; void print_even (int x) { if (x % 2 == 0) std::cout << x << " is even\n"; else throw (std::logic_error("this thread param not even")); } void print_thread_id (int id) { try { std::lock_guard<std::mutex> lck (mtx); print_even(id); } catch (std::logic_error&) { std::cout << "[exception caught]\n"; } } int main () { std::thread threads[10]; // spawn 10 threads: for (int i=0; i<10; ++i) threads[i] = std::thread(print_thread_id,i+1); for (auto& th : threads) th.join(); return 0; }
运行结果:
std::lock_guard 虽然用起来方便,但是不够灵活,它只能在析构函数中 unlock(),也就是对象被释放的时候,这通常是在函数返回的时候,或者通过添加代码块 { /* 代码块 */ } 限定作用域来指定释放时机。
5.1 unique_lock
unique_lock 也是类模板,功能和lock_guard类似。unique_lock 虽然率会低一点,内存占用量相对高一些,但是比lock_guard更灵活。uniqie_lock 的缺省用法实际上与 lock_quard 一样,可以直接替换。
#include <iostream> #include <deque> #include <thread> #include <mutex> #include <condition_variable> #include <unistd.h> std::deque<int> q; std::mutex mu; std::condition_variable cond; int count = 0; void fun1() { while (true) { { // std::lock_guard<std::mutex> locker(mu); std::unique_lock<std::mutex> locker(mu); q.push_front(count++); // locker.unlock(); // 这里是不是必须的? cond.notify_one(); // } sleep(1); } } void fun2() { while (true) { std::unique_lock<std::mutex> locker(mu); cond.wait(locker, [](){return !q.empty();}); auto data = q.back(); q.pop_back(); // // locker.unlock(); // 这里是不是必须的? std::cout << "thread2 get value form thread1: " << data << std::endl; } } int main() { std::thread t1(fun1); std::thread t2(fun2); t1.join(); t2.join(); return 0; }
六、异步操作
6.1 std::future 和 std::aysnc 使用举例
std::future 异步获取任务函数的返回值;std::aysnc 异步执行某个任务函数
#include <iostream> #include <future> #include <thread> using namespace std; int find_result_to_add() { std::this_thread::sleep_for(std::chrono::seconds(2)); // 用来测试异步延迟的影 响 std::cout << "find_result_to_add" << std::endl; return 1 + 1; } string find_result_string() { std::this_thread::sleep_for(std::chrono::seconds(2)); // 用来测试异步延迟的影 响 std::cout << "find_result_string" << std::endl; return "lwang"; } int find_result_to_add2(int a, int b) { std::this_thread::sleep_for(std::chrono::seconds(5)); // 用来测试异步延迟的影 响 return a + b; } string find_result_to_string2(string val) { std::this_thread::sleep_for(std::chrono::seconds(5)); // 用来测试异步延迟的影 响 return val; } void do_other_things() { std::cout << "do_other_things" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟其他任务耗时 } int main() { // 三种写法 std::async(func(),Args) 异步执行func函数,自己内部有线程 // std::future<int> result = std::async(find_result_to_add); // 第一种 // std::future<decltype (find_result_string())> result = std::async(find_result_string); // 第二种 auto result = std::async(find_result_to_add); // 推荐的写法 第三种 do_other_things(); std::cout << "result: " << result.get() << std::endl; // 这里get()会阻塞, 等待函数的返回值。 // 如果任务函数有参数,参数不能写类型,需要写具体类型的值!!! // std::future<decltype (find_result_to_add2(int, int))> result2 = std::async(find_result_to_add2, 10, 20); //错误 std::future<decltype (find_result_to_add2(0, 0))> result2 = std::async(find_result_to_add2, 10, 20); std::cout << "result2: " << result2.get() << std::endl; // std::future<decltype (find_result_to_string2(string))> result3 = std::async(find_result_to_string2, "lwang"); //错误 std::future<decltype (find_result_to_string2(""))> result3 = std::async(find_result_to_string2, "lwang"); std::cout << "result3: " << result3.get() << std::endl; // 延迟是否有影响? std::cout << "main finish" << endl; return 0; }
6.2 std::packaged_task 使用举例
std::packaged_task 将任务和feature绑定在一起的模板,对任务的封装
#include <iostream> #include <future> using namespace std; int add(int a, int b, int c) { std::cout << "call add\n"; return a + b + c; } void do_other_things() { std::cout << "do_other_things" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } int main() { std::packaged_task<int(int, int, int)> task(add); // 封装任务,还没有执行 do_other_things(); std::future<int> result = task.get_future(); task(1, 1, 2); //必须执行任务,否则在get()获取future的值时会一直阻塞 std::cout << "result:" << result.get() << std::endl; return 0; }
6.3 promise 使用举例
作用:std::promise 提供了一种设置值的方式,通过相关联的future对象可以读取设置值。
#include <future> #include <string> #include <thread> #include <iostream> using namespace std; void print(std::promise<std::string>& p) { p.set_value("There is the result whitch you want."); } void do_some_other_things() { std::cout << "Hello World" << std::endl; } int main() { std::promise<std::string> promise; std::future<std::string> result = promise.get_future(); std::thread t(print, std::ref(promise)); // 创建线程t,去设置promise对象的值 do_some_other_things(); std::cout << result.get() << std::endl; // future 获取 promise 返回值 t.join(); }
运行结果:
分析:在 promise 创建好的时候,future也创建好了。主线程持有future,阻塞等待,随时获取值。
文章参考于<零声教育>的C/C++linux服务期高级架构。