C++11学习
一、多线程
1、模板线程是以右值传递的
template <class Fn, class... Args> explicit thread(Fn&& fn, Args&&... args)
则需要使用到std::ref
和std::cref
很好地解决了这个问题,std::ref
可以包装按引用传递的值。std::cref
可以包装按const引用传递的值。
// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include <iostream>
#include <thread>
using namespace std;
template<class T> void changevalue(T &x, T val) {
x = val;
}
int main() {
thread th[100];
int nums[100];
for (int i = 0; i < 100; i++)
th[i] = thread(changevalue<int>, ref(nums[i]), i+1);
for (int i = 0; i < 100; i++) {
th[i].join();
cout << nums[i] << endl;
}
return 0;
}
2、线程一些注意事项
线程是在thread对象被定义的时候开始执行的,而不是在调用join函数时才执行的,调用join函数只是阻塞等待线程结束并回收资源。
分离的线程(执行过detach的线程)会在调用它的线程结束或自己结束时释放资源。
线程会在函数运行完毕后自动释放,不推荐利用其他方法强制结束线程,可能会因资源未释放而导致内存泄漏。
没有执行join或detach的线程在程序结束时会引发异常
3、std::mutex互斥锁
三个方法
lock 上锁
unlock 解锁
bool try_lock() 尝试将mutex上锁。如果mutex未被上锁,则将其上锁并返回true;如果mutex已被锁则返回false。
4、std::atomic & atomic_flag
意思是该类型的变量是原子类型的操作。因为mutex的资源浪费较大,总是要上锁解锁的。引入atomic
std::atomic_int`只是`std::atomic<int>
atomic_int n = 0; 表示定义一个int类型的原子属性的变量n,他是线程安全的
atomic() noexcept = default | 默认构造函数 | 构造一个atomic对象(未初始化,可通过atomic_init进行初始化) |
---|---|---|
constexpr atomic(T val) noexcept | 初始化构造函数 | 构造一个atomic对象,用val 的值来初始化 |
atomic_flag,原子布尔类型,不同于 std::atomic , std::atomic_flag
不提供加载或存储操作。
成员函数
(构造函数) | 构造 atomic_flag (公开成员函数) |
---|---|
operator= | 赋值运算符 (公开成员函数) |
clear | 原子地设置标志为 false (公开成员函数) |
test_and_set | 原子地设置标志为 true 并获得其先前值 (公开成员函数) |
#include <thread>
#include <vector>
#include <iostream>
#include <atomic>
//ATOMIC_FLAG_INIT 置为false
std::atomic_flag lock = ATOMIC_FLAG_INIT;
void f(int n)
{
for (int cnt = 0; cnt < 100; ++cnt) {
//std::memory_order_acquire 内存访问
while (lock.test_and_set(std::memory_order_acquire)) // 获得锁
; // 自旋
std::cout << "Output from thread " << n << '\n';
lock.clear(std::memory_order_release); // 释放锁
}
}
int main()
{
std::vector<std::thread> v;
for (int n = 0; n < 10; ++n) {
v.emplace_back(f, n);
}
for (auto& t : v) {
t.join();
}
}
5、std::async&std::future&std::shared_future
std::async定义在future
头文件中,新的线程类,执行效率更高于thread,并且可以选择同步异步执行,还有返回值可以选择调用时间。
重载版本 | 作用 |
---|---|
template <class Fn, class… Args> future<typename result_of<Fn(Args…)>::type> async (Fn&& fn, Args&&… args) |
异步或同步(根据操作系统而定)以args为参数执行fn 同样地,传递引用参数需要 std::ref 或std::cref
|
template <class Fn, class… Args> future<typename result_of<Fn(Args…)>::type> async (launch policy, Fn&& fn, Args&&… args); |
异步或同步(根据policy 参数而定(见下文))以args为参数执行fn,引用参数同上。std::launch有2个枚举值和1个特殊值:枚举值:launch::async、launch::deferred、launch::async |
// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include <iostream>
#include <thread>
#include <future>
using namespace std;
int main() {
async(launch::async, [](const char *message){
cout << message << flush;
}, "Hello, ");
cout << "World!" << endl;
return 0;
}
其async的返回值std::future,是一个模板对象。
其成员函数有
一般:T get() 当类型为引用:R& future<R&>::get() 当类型为void:void future::get() |
阻塞等待线程结束并获取返回值。 若类型为void,则与 future::wait() 相同。只能调用一次。 |
---|---|
void wait() const | 阻塞等待线程结束 |
template <class Rep, class Period> future_status wait_for(const chrono::duration<Rep,Period>& rel_time) const; |
阻塞等待rel_time(rel_time是一段时间), 若在这段时间内线程结束则返回future_status::ready 若没结束则返回future_status::timeout 若async是以launch::deferred启动的,则不会阻塞并立即返回future_status::deferred |
std::future的作用并不只有获取返回值,它还可以检测线程是否已结束、阻塞等待,所以对于返回值是void的线程来说,future也同样重要。
// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include <iostream>
#include <future>
using namespace std;
void count_big_number() {
// C++14标准中,可以在数字中间加上单
// 引号 ' 来分隔数字,使其可读性更强
for (int i = 0; i <= 10'0000'0000; i++);
}
int main() {
future<void> fut = async(launch::async, count_big_number);
cout << "Please wait" << flush;
// 每次等待1秒
while (fut.wait_for(chrono::seconds(1)) != future_status::ready)
cout << '.' << flush;
cout << endl << "Finished!" << endl;
return 0;
}
std::shared_future类模板 std::shared_future
提供访问异步操作结果的机制,类似 std::future ,除了允许多个线程等候同一共享状态。不同于仅可移动的 std::future (故只有一个实例能指代任何特定的异步结果),std::shared_future
可复制而且多个 shared_future 对象能指代同一共享状态。
若每个线程通过其自身的 shared_future
对象副本访问,则从多个线程访问同一共享状态是安全的。
#include <iostream>
#include <future>
#include <chrono>
int main()
{
std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise;
std::shared_future<void> ready_future(ready_promise.get_future());
std::chrono::time_point<std::chrono::high_resolution_clock> start;
auto fun1 = [&, ready_future]() -> std::chrono::duration<double, std::milli>
{
t1_ready_promise.set_value();
ready_future.wait(); // 等待来自 main() 的信号
return std::chrono::high_resolution_clock::now() - start;
};
auto fun2 = [&, ready_future]() -> std::chrono::duration<double, std::milli>
{
t2_ready_promise.set_value();
ready_future.wait(); // 等待来自 main() 的信号
return std::chrono::high_resolution_clock::now() - start;
};
auto result1 = std::async(std::launch::async, fun1);
auto result2 = std::async(std::launch::async, fun2);
// 等待线程变为就绪
t1_ready_promise.get_future().wait();
t2_ready_promise.get_future().wait();
// 线程已就绪,开始时钟
start = std::chrono::high_resolution_clock::now();
// 向线程发信使之运行
ready_promise.set_value();
std::cout << "Thread 1 received the signal "
<< result1.get().count() << " ms after start\n"
<< "Thread 2 received the signal "
<< result2.get().count() << " ms after start\n";
}
6、std::promise
是实际上是std::future的一个包装,但是作用确实想实现线程函数的左值引用,将函数执行结果带回来。
一般: void set_value (const T& val) void set_value (T&& val) 当类型为引用:void promise<R&>::set_value (R& val) 当类型为void:void promise::set_value (void) |
设置promise的值并将共享状态设为ready(将future_status设为ready) void特化:只将共享状态设为ready |
---|---|
future get_future() | 构造一个future对象,其值与promise相同,status也与promise相同 |
// Compiler: MSVC 19.29.30038.1
// C++ Standard: C++17
#include <iostream>
#include <thread>
#include <future> // std::promise std::future
using namespace std;
template<class ... Args> decltype(auto) sum(Args&&... args) {
return (0 + ... + args);
}
template<class ... Args> void sum_thread(promise<long long> &val, Args&&... args) {
val.set_value(sum(args...)); //设置值
}
int main() {
promise<long long> sum_value;
//目的还是传递左值引用
thread get_sum(sum_thread<int, int, int>, ref(sum_value), 1, 10, 100);
cout << sum_value.get_future().get() << endl; //获取值
get_sum.join(); // 感谢评论区 未来想做游戏 的提醒
return 0;
}
6、std::this_thread中的一些静态方法
std::thread::id get_id() noexcept | 获取当前线程id |
---|---|
template<class Rep, class Period> void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration ) |
等待sleep_duration (sleep_duration 是一段时间) |
void yield() noexcept |
暂时放弃线程的执行,将主动权交给其他线程 (放心,主动权还会回来) |
template <class Clock, class Duration> void sleep_until (const chrono::time_point<Clock,Duration>& abs_time); | 等到到abs_time这个时间到来再执行 |
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic_bool ready = 0; //原子类型变量 bool类型 atomic<bool>
// uintmax_t ==> unsigned long long
void sleep(uintmax_t ms) {
this_thread::sleep_for(chrono::milliseconds(ms));
}
void count() {
while (!ready) this_thread::yield();//暂时交出cpu执行,
for (int i = 0; i <= 20'0000'0000; i++);
cout << "Thread " << this_thread::get_id() << " finished!" << endl;
return;
}
int main() {
thread th[10];
for (int i = 0; i < 10; i++)
th[i] = thread(::count);
sleep(5000);
ready = true;
cout << "Start!" << endl;
for (int i = 0; i < 10; i++)
th[i].join();
return 0;
}
// this_thread::sleep_for example
#include <iostream> // std::cout
#include <iomanip> // std::put_time
#include <thread> // std::this_thread::sleep_until
#include <chrono> // std::chrono::system_clock
#include <ctime> // std::time_t, std::tm, std::localtime, std::mktime
int main()
{
using std::chrono::system_clock;
std::time_t tt = system_clock::to_time_t (system_clock::now());
struct std::tm * ptm = std::localtime(&tt);
std::cout << "Current time: " << std::put_time(ptm,"%X") << '\n';
std::cout << "Waiting for the next minute to begin...\n";
++ptm->tm_min; ptm->tm_sec=0;
std::this_thread::sleep_until (system_clock::from_time_t (mktime(ptm)));
std::cout << std::put_time(ptm,"%X") << " reached!\n";
return 0;
}
7、std::lock_guard & std::unique_lock
成员函数
(构造函数) | 构造 lock_guard ,可选地锁定给定的互斥 (公开成员函数) |
---|---|
(析构函数) | 析构 lock_guard 对象,解锁底层互斥 (公开成员函数) |
lock_guard( mutex_type& m, std::adopt_lock_t t );的构造方法的第二个参数
std::defer_lock_t, std::try_to_lock_t, std::adopt_lock_t 三个值。是用于为 std::lock_guard 、 std::scoped_lock 、 std::unique_lock 和 std::shared_lock 指定锁定策略的空类标签类型。
defer_lock_t
不获得互斥的所有权try_to_lock_t
尝试获得互斥的所有权而不阻塞adopt_lock_t
假设调用方线程已拥有互斥的所有权
构造互斥锁的写法,就是会在lock_guard构造函数里加锁,在析构函数里解锁,达到自动加锁,解锁的目的。因此lock_guard的作用域就是锁的范围。
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
using namespace std;
mutex mt;
void thread_task()
{
for (int i = 0; i < 10; i++)
{
lock_guard<mutex> guard(mt);
cout << "print thread: " << i << endl;
}
}
int main()
{
thread t(thread_task);
for (int i = 0; i > -10; i--)
{
lock_guard<mutex> guard(mt);
cout << "print main: " << i << endl;
}
t.join();
return 0;
}
虽然lock_guard挺好用的,但是有个很大的缺陷,在定义lock_guard的地方会调用构造函数加锁,在离开定义域的话lock_guard就会被销毁,调用析构函数解锁。这就产生了一个问题,如果这个定义域范围很大的话,那么锁的粒度就很大,很大程序上会影响效率。所以为了解决lock_guard锁的粒度过大的原因,unique_lock就出现了。
unique_lock<mutex> unique(mt);
这个会在构造函数加锁,然后可以利用unique.unlock()来解锁,所以当你觉得锁的粒度太多的时候,可以利用这个来解锁,而析构的时候会判断当前锁的状态来决定是否解锁,如果当前状态已经是解锁状态了,那么就不会再次解锁,而如果当前状态是加锁状态,就会自动调用unique.unlock()来解锁。而lock_guard在析构的时候一定会解锁,也没有中途解锁的功能。方便肯定是有代价的,unique_lock内部会维护一个锁的状态,所以在效率上肯定会比lock_guard慢。
(构造函数) | 构造 unique_lock ,可选地锁定提供的互斥 (公开成员函数) |
---|---|
(析构函数) | 若占有关联互斥,则解锁之 (公开成员函数) |
operator= | 若占有则解锁互斥,并取得另一者的所有权 (公开成员函数) |
锁定 | |
lock | 锁定关联互斥 (公开成员函数) |
try_lock | 尝试锁定关联互斥,若互斥不可用则返回 (公开成员函数) |
try_lock_for | 试图锁定关联的可定时锁定 (TimedLockable) 互斥,若互斥在给定时长中不可用则返回 (公开成员函数) |
try_lock_until | 尝试锁定关联可定时锁定 (TimedLockable) 互斥,若抵达指定时间点互斥仍不可用则返回 (公开成员函数) |
unlock | 解锁关联互斥 (公开成员函数) |
修改器 | |
swap | 与另一 std::unique_lock 交换状态 (公开成员函数) |
release | 将关联互斥解关联而不解锁它 (公开成员函数) |
观察器 | |
mutex | 返回指向关联互斥的指针 (公开成员函数) |
owns_lock | 测试锁是否占有其关联互斥 (公开成员函数) |
operator bool | 测试锁是否占有其关联互斥 |
#include <mutex>
#include <thread>
#include <chrono>
struct Box {
explicit Box(int num) : num_things{num} {}
int num_things;
std::mutex m;
};
void transfer(Box &from, Box &to, int num)
{
// 仍未实际取锁
std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
// 锁两个 unique_lock 而不死锁
std::lock(lock1, lock2);
from.num_things -= num;
to.num_things += num;
// 'from.m' 与 'to.m' 互斥解锁于 'unique_lock' 析构函数
}
int main()
{
Box acc1(100);
Box acc2(50);
std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);
t1.join();
t2.join();
}
8、condition_variable 条件变量
condition_variable
类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable
。文章来源:https://uudwc.com/A/rZ6W6
-------------有意修改变量的线程必须
获得 std::mutex (常通过 std::lock_guard )
在保有锁时进行修改
在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁)
即使共享变量是原子的,也必须在互斥下修改它,以正确地发布修改到等待的线程。
-----------任何有意在 std::condition_variable 上等待的线程必须
在与用于保护共享变量者相同的互斥上获得 std::unique_lock<std::mutex>
执行下列之一:
检查条件,是否为已更新或提醒它的情况
执行 wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行。
condition_variable 被通知时,时限消失或虚假唤醒发生,线程被唤醒,且自动重获得互斥。之后线程应检查条件,若唤醒是虚假的,则继续等待。
或者
使用 wait 、 wait_for 及 wait_until 的有谓词重载,它们包揽以上三个步骤
std::condition_variable
只可与 std::unique_lock<std::mutex> 一同使用文章来源地址https://uudwc.com/A/rZ6W6
成员函数
(构造函数) | 构造对象 (公开成员函数) |
---|---|
(析构函数) | 析构对象 (公开成员函数) |
operator=[被删除] | 不可复制赋值 (公开成员函数) |
通知 | |
notify_one | 通知一个等待的线程 (公开成员函数) |
notify_all | 通知所有等待的线程 (公开成员函数) |
等待 | |
wait | 阻塞当前线程,直到条件变量被唤醒 (公开成员函数) |
wait_for | 阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后 (公开成员函数) |
wait_until | 阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点 (公开成员函数) |
原生句柄 | |
native_handle | 返回原生句柄 |
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
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++多线程,涉及5个头文件
thread.h
thread 类 (成员方法)
thread 构造函数
join 等待结束
detach 分离线程,则无需等待结束
this_thread 命名空间(静态方法)
get_id 获取当前线程id
yield 暂时交出cpu执行权
sleep_for 睡眠等待函数
sleep_until 等待到下一个具体的时间
mutex.h
mutex 互斥锁
lock_guard 配合互斥锁,达到互斥锁作用域范围内的自动上锁解锁
unique_lock 在lock_guard基础上加了可以手动解锁的接口unlock
atomic.h
atomic 原子化变量
atomic_flag 原子布尔类型
condition_variable.h
condition_variable
wait 阻塞当前线程,直到条件变量被唤醒 (公开成员函数)
wait_for 阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后 (公开成员函数)
wait_until 阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点 (公开成员函数)
notify_ont 通知一个等待的线程 (公开成员函数)
notify_all 通知所有等待的线程 (公开成员函数)
condition_variable_any std::condition_variable 的泛化
wait
wait_for
wait_until
notify_ont
notify_all
future.h
future 类似一个针对线程的类模板对象,可以对线程进行管理的。
shared_future 不同于仅可移动的 std::future (故只有一个实例能指代任何特定的异步结果),std::shared_future 可复制而且多个 shared_future 对象能指代同一共享状态。
promise 类似future
packaged_task