线程类Thread
- 常用的一个构造函数:explicit thread(_Callable&& __f, _Args&&... __args)
- f的类型为普通的类成员函数示例:
class Test { public: void introduce(std::string name, int age) { std::cout << "name:" << name << ",age:" << age << std::endl; } }; int main() { Test test; // 第二个参数是传递给普通成员函数的this指针的 std::thread t(&Test::introduce, &test, "NrvCer", 23); t.join(); return 0; }
- f的类型为静态的类成员函数示例:
#include <iostream> #include <thread> #include <string> class Test { public: static void introduce(std::string name, int age) { std::cout << "name:" << name << ",age:" << age << std::endl; } }; int main() { std::thread t(Test::introduce, "NrvCer", 23); t.join(); return 0; }
- f的类型为仿函数
#include <iostream> #include <thread> #include <string> #include <functional> class Test { public: void introduce(std::string name, int age) { std::cout << "name:" << name << ",age:" << age << std::endl; } }; int main() { Test test; std::thread t(std::bind(&Test::introduce, &test, "NrvCer", 23)); t.join(); return 0; }
- f的类型为匿名函数:例如lambda表达式
class Test { public: void introduce(std::string name, int age) { std::cout << "name:" << name << ",age:" << age << std::endl; } }; int main() { Test test; std::thread t([&test]() { test.introduce("NrvCer", 23); }); t.join(); return 0; }
- 公共静态成员函数之get_id:获取线程id
- 公共成员函数之join:阻塞调用join函数的线程。一般在主线程中调用这个函数,阻塞主线程。当子线程任务处理完毕,join会清理子线程中的相关资源然后返回。然后调用join的主线程才能解除阻塞继续向下执行。
- detach:进行线程分离,分离主线程和创造出的子线程。在线程分离之后,主线程退出也会一并销毁创建出的所有子线程
- joinable:判断主线程和子线程是否处于关联状态。函数返回值为true,表示主线程和子线程之间有关联关系;返回值为false,表示主线程和子线程之间没有关联关系。一般先使用这个函数判断某个线程是否可以join。
- 静态函数hardware_concurrency:获取当前计算机的CPU核心数
// 12
std::cout << std::thread::hardware_concurrency() << std::endl;
线程的命名空间this_thread
c++11的线程的命名空间std::this_thread中提供了四个公共的成员函数。
- get_id:获取当前线程的线程id。
- 函数原型如下:
thread::id get_id() noexcept;
- 函数使用示例:
#include <thread> #include <iostream> void func() { std::cout << "子线程id:" << std::this_thread::get_id() << std::endl; } int main() { std::cout << "主线程id:" << std::this_thread::get_id() << std::endl; std::thread t(func); t.join(); return 0; }
- sleep_for函数:调用这个函数的线程将从运行态变为阻塞态,在阻塞态下休眠一定的时长。
- 函数原型如下:
template<typename _Rep, typename _Period> inline void sleep_for(const chrono::duration<_Rep, _Period>& __rtime)
- 函数使用示例:
#include <thread> #include <iostream> void func() { for (int i = 0; i < 5; ++i) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "子线程id:" << std::this_thread::get_id() << ",i = " << i << std::endl; } } int main() { std::thread t(func); t.join(); return 0; }
- sleep_until函数:指定调用这个函数的线程阻塞到指定的时间点
- 函数原型如下:
template<typename _Clock, typename _Duration> inline void sleep_until(const chrono::time_point<_Clock, _Duration>& __atime)
- 函数使用示例
#include <thread> #include <iostream> void func() { for (int i = 0; i < 5; ++i) { auto now = std::chrono::system_clock::now(); std::chrono::seconds sec(2); std::this_thread::sleep_until(now + sec); std::cout << "子线程id:" << std::this_thread::get_id() << ",i = " << i << std::endl; } } int main() { std::thread t(func); t.join(); return 0; }
- yield函数:在线程中调用这个函数之后,处于运行态的线程会主动让出自己已经抢到的 CPU 时间片,最终变为就绪态。但是在下一个时间片中,这个处于就绪态的线程还是会继续争抢时间片的。
- 函数原型:
inline void yield() noexcept
- 函数使用示例
// 如果当前线程没有运算完毕就放弃当前线程占用的时间片 while (!isDone()) { std::this_thread::yield(); }
call_once函数
- call_once函数用来保证内部的回调函数只会被执行一次。
- 该函数原型如下:
void call_once(once_flag& __once, _Callable&& __f, _Args&&... __args) 函数参数: once:要保证这个对象能够被多个线程同时访问到 f:回调函数,可以传递一个有名函数地址,也可以指定一个匿名函数 args:作为实参传递给回调函数
- 示例
#include <iostream> #include <thread> #include <string> #include <mutex> // 未经初始化的全局变量位于bss段,所有线程共享 std::once_flag g_flag; void execute_output(std::string name, int age) { std::cout << "name:" << name << ",age:" << age << std::endl; } void func(std::string name, int age) { static int count = 0; // execute_output只被执行一次 std::call_once(g_flag, execute_output, name, age); // func被执行两次 std::cout << "func execute count:" << ++count << std::endl; } int main() { std::thread t1(func, "NrvCer", 23); std::thread t2(func, "NrvCer", 23); t1.join(); t2.join(); return 0; }
- call_once的应用:使用懒汉模式创建单例模式的类
#include <iostream>
#include <mutex> // for call_once
#include <thread> // for class thread
std::once_flag g_flag;
class Singleton {
public:
static Singleton* getInstance() {
std::call_once(g_flag, [&]() {
obj = new Singleton();
std::cout << "initial success\n";
});
return obj;
}
private:
static Singleton* obj;
};
Singleton* Singleton::obj = nullptr;
void func() {
std::cout << Singleton::getInstance() << std::endl;
}
int main() {
// test
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
return 0;
}
线程同步之互斥锁
c++11中一共提供了四种互斥锁。分别是:
- mutex:这是独占的互斥锁,不能递归使用
- timed_mutex:带超时的独占互斥锁,不能递归使用
- recursive_mutex:递归互斥锁,不带超时功能
- recursive_timed_mutex:带超时的递归互斥锁
补充:C++14
中提供了带超时的可共享互斥锁shared_timed_mutex
,C++17
中提供了共享的互斥锁shared_mutex。
1.std::mutex
- mutex是一种独占互斥锁,互斥锁对象的个数和共享资源的个数相等,它和线程的个数没有关系。
- std::mutex类中常用的成员函数:
- lock:用于给临界区加锁,并且只能有一个线程获得锁的所有权,它有阻塞线程的作用
- unlock:解除对临界区的锁定
- try_lock:尝试给临界区加锁。这个函数和lock的区别就是这个函数不会阻塞线程。
- 示例:两个线程数数,从1顺序数到20。如果需要交替数数则使用条件变量或者信号量
#include <iostream> #include <thread> #include <mutex> int g_number = 0; std::mutex m; void count(int id) { for (int i = 0; i < 10; ++i) { m.lock(); g_number++; std::cout << id << ":" << g_number << std::endl; // 需要保证unlock操作执行,否则导致其中一个子线程永远获取不到锁 m.unlock(); // 阻塞当前线程给其他线程机会 std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } int main() { std::thread t1(count, 0); std::thread t2(count, 1); t1.join(); t2.join(); return 0; }
- std::lock_guard:使用这个类,可以简化使用互斥锁的lock和unlock方法。原理是使用
explicit lock_guard(mutex_type& __m)
构造函数构造lock_guard对象时,会自动锁定互斥量;当lock_guard对象退出作用域时,析构函数内部会自动解除对互斥量的锁定。- 示例
// 使用lock_guard对上述代码进行改进 void count(int id) { for (int i = 0; i < 10; ++i) { // 使用作用域块控制互斥锁的锁粒度 { std::lock_guard<std::mutex> lock(m); g_number++; std::cout << id << ":" << g_number << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } }
- lock_guard的优缺点:
- 缺点是:锁的粒度不能进行灵活控制,相比于lock方法和unlock方法,锁的粒度变大了。但是可以通过将lock_guard相关代码放在代码块中减少锁的粒度。
- 优点是:不用手动进行互斥锁的释放
- unique_lock:相比于lock_gaurd,使用这个类可以进一步减少锁的粒度,因为这个类提供了更多的成员函数
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
int global_var = 0;
mutex m1;
void func() {
for (int i = 0; i < 1000000; i++) {
unique_lock<mutex> lock(m1);
global_var++;
global_var--;
// 虽然lock对象生命周期结束,会自动释放锁,但是
// 得益于它的成员函数,这里进一步减少锁的粒度,在业务代码执行前释放锁
if (lock.owns_lock()) {
lock.unlock();
}
// 业务代码...
}
}
int main() {
thread t1(func);
thread t2(func);
t1.join();
t2.join();
printf("%d\n", global_var);// 0
return 0;
}
2.std::recursive_mutex
- std::recursive_mutex:这是递归互斥锁,它允许同一线程多次获得互斥锁,可以用来解决同一个线程需要多次获得互斥量时导致死锁的问题。
- 问题演示:使用非递归独占互斥量发生死锁
#include <iostream> #include <mutex> #include <thread> std::mutex m; int g_num = 0; void func() { m.lock(); // 这里为了方便演示,实际上可能调用一个方法,方法内部再次对独占互斥锁进行了锁定 m.lock(); g_num++; m.unlock(); } int main() { std::thread t1(func); t1.join(); return 0; }
- 上述问题的解决方案:使用递归互斥锁,同时解锁的次数需要和上锁的次数相同。
// 将上述的非递归独占锁换成递归锁 std::recursive_mutex m;
- 递归互斥锁的缺点:
- 它的效率比非递归互斥锁低些
- 递归互斥锁虽然允许同一个线程多次获得同一个互斥锁的所有权,但最大次数并未具体说明,一旦超过一定的次数,就会抛出std::system错误。
3.std::timed_mutex
- timed_mutex:这是超时独占互斥锁,在获取互斥锁资源时,如果使用普通的互斥锁,线程可能一直阻塞下去。而使用超时独占互斥锁,线程阻塞了一个超时时长后,就会解除阻塞。
- timed_mutex:相比于普通独占互斥锁,多了两个成员函数:
- try_lock_for
// 当线程获取不到互斥锁资源的时候,让线程阻塞一定的时间长度 template <class _Rep, class _Period> bool try_lock_for(const chrono::duration<_Rep, _Period>& __rtime) 函数返回值: 当获得互斥锁的所有权后,就解除阻塞返回true 阻塞的时长用完就返回false
- try_lock_until
// 当线程获取不到互斥锁资源的时候,让线程阻塞到某一个指定的时间点 template <class _Clock, class _Duration> bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime) 函数返回值: 当获得互斥锁的所有权后,就解除阻塞返回true 阻塞到函数指定的时间点就返回false
- 使用示例
#include <thread>
#include <mutex>
#include <iostream>
class Test {
private:
std::timed_mutex _m;
int _i;
public:
void worker() {
while (1) {
if (_m.try_lock_for(std::chrono::milliseconds(10))) {
std::cout << "子线程id:" << std::this_thread::get_id() << "获取互斥锁所有权成功\n";
// 对临界资源进行读写操作
this->_i++;
// 模拟处理其他业务
std::this_thread::sleep_for(std::chrono::seconds(5));
_m.unlock();
break;
} else {
std::cout << "子线程id:" << std::this_thread::get_id() << "获取互斥锁所有权失败\n";
}
}
}
Test(int i) : _i(i) {}
};
int main() {
Test test(0);
std::thread t1(&Test::worker, &test);
std::thread t2(&Test::worker, &test);
t1.join();
t2.join();
return 0;
}
4.std::shared_mutex
- 这是共享的互斥锁,适用于存在多个线程对共享资源读、少许线程对共享资源写的情况,使用共享互斥锁比普通互斥锁效率更高。等同于glibc库中的读写锁
线程同步之条件变量
条件变量能够阻塞一个或者多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程,条件变量需要和互斥量结合使用,防止多个线程同时对同一块内存进行读写操作。c++11中条件变量有两种:
- condition_variable
- condition_variable_any
1.condition_variable
- 等待函数wait:如果线程被函数阻塞,这个线程会释放占有的互斥锁的所有权,当阻塞解除之后这个线程会重新得到互斥锁的所有权,继续向下执行
#include <condition_variable>
// 调用wait函数的线程会被阻塞
void wait(unique_lock<mutex>& __lock) noexcept;
template<typename _Predicate>
void wait(unique_lock<mutex>& __lock, _Predicate __p)
函数参数:
pred:是一个谓词,返回false则当前线程被阻塞,返回true则当前线程不会阻塞
- wait_for函数:和wait函数功能一样,区别在于假设当前阻塞的线程没有被其他线程唤醒,当阻塞一定的时长后,线程就会自动解除阻塞,继续向下运行。
cv_status
wait_for(unique_lock<mutex>& __lock,
const chrono::duration<_Rep, _Period>& __rtime)
template<typename _Rep, typename _Period, typename _Predicate>
bool
wait_for(unique_lock<mutex>& __lock,
const chrono::duration<_Rep, _Period>& __rtime,
_Predicate __p)
- wait_until函数:指定让线程阻塞到某一个时间点,假设阻塞的线程没有被其他线程唤醒,当到达指定的时间点之后,线程就会自动解除阻塞,继续向下执行。
template<typename _Duration>
cv_status
wait_until(unique_lock<mutex>& __lock,
const chrono::time_point<steady_clock, _Duration>& __atime)
template<typename _Clock, typename _Duration, typename _Predicate>
bool
wait_until(unique_lock<mutex>& __lock,
const chrono::time_point<_Clock, _Duration>& __atime,
_Predicate __p)
- 通知函数:
// 唤醒一个被当前条件变量阻塞的线程
void notify_one() noexcept;
// 唤醒全部被当前条件变量阻塞的线程
void notify_all() noexcept;
- 有关unique_lock的方法
// 锁定关联的互斥锁
void lock();
// 尝试锁定关联的互斥锁,若无法锁定,函数直接返回
bool try_lock();
// 试图锁定关联的可定时互斥锁,若互斥锁在给定时长中仍不能被锁定,函数返回
bool try_lock_for(const chrono::duration<_Rep, _Period>& __rtime);
// 试图锁定关联的可定时互斥锁,若互斥锁在到达给定的时间点后仍不能被锁定,函数返回
bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime);
// 将互斥锁解锁
void unlock();
- 使用condition_variable实现生产者消费者模型示例
// 使用条件变量实现一个生产者消费者模型
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <thread>
class TaskQueue {
private:
int capacity; // 任务队列的容量
std::mutex m; // 独占互斥锁
std::condition_variable full; // 阻塞生产者线程的条件变量
std::condition_variable empty; // 阻塞消费者线程的条件变量
std::queue<int> m_queue; // 存储任务的队列,任务为int型数据
public:
TaskQueue(int capacity) : capacity(capacity) {}
void put(const int& x) {
std::unique_lock<std::mutex> locker(m);
// 使用while是为了防止虚假唤醒的问题
while (m_queue.size() == this->capacity) {
full.wait(locker);
}
this->m_queue.push(x);
std::cout << "生产者线程id:" << std::this_thread::get_id() << ",put x:" << x << std::endl;
empty.notify_all();
}
void take() {
std::unique_lock<std::mutex> locker(m);
while (m_queue.empty()) {
empty.wait(locker);
}
int x = this->m_queue.front();
m_queue.pop();
std::cout << "消费者线程id:" << std::this_thread::get_id() << ",take x:" << x << std::endl;
full.notify_all();
}
// 获取当前任务队列的大小
int size() {
std::lock_guard<std::mutex> locker(m);
return this->m_queue.size();
}
// 判断是否任务队列为空
bool isEmpty() {
std::lock_guard<std::mutex> locker(m);
return m_queue.empty();
}
// 判断任务队列是否为满
bool isFull() {
std::lock_guard<std::mutex> locker(m);
return m_queue.size() == this->capacity;
}
};
int main() {
TaskQueue task(100);
// 生产者线程、消费者线程各5个
std::thread producer[5], consume[5];
for (int i = 0; i < 5; ++i) {
producer[i] = std::thread(&TaskQueue::put, &task, i + 100);
consume[i] = std::thread(&TaskQueue::take, &task);
}
for (int i = 0; i < 5; ++i) {
producer[i].join();
consume[i].join();
}
return 0;
}
- 使用另一个wait方法对上述程序进行改进:根据谓词判断线程是否需要阻塞。这么改进的优点是程序更加精简,没有了while循环;缺点是程序的可读性变差。
void put(const int& x) {
std::unique_lock<std::mutex> locker(m);
full.wait(locker, [this]() {
return this->m_queue.size() != this->capacity;
});
this->m_queue.push(x);
std::cout << "生产者线程id:" << std::this_thread::get_id() << ",put x:" << x << std::endl;
empty.notify_all();
}
void take() {
std::unique_lock<std::mutex> locker(m);
empty.wait(locker, [this]() {
return this->m_queue.size() != 0;
});
int x = this->m_queue.front();
m_queue.pop();
std::cout << "消费者线程id:" << std::this_thread::get_id() << ",take x:" << x << std::endl;
full.notify_all();
}
2. condition_variable_any
- 线程阻塞函数:
- wait:可以传递给wait函数的互斥锁有前一节提到的四种互斥锁(任意一种互斥锁)
- wait_for
- wait_until
- 线程唤醒函数:
void notify_one() noexcept;
void notify_all() noexcept;
- 使用condition_variable_any实现生产者消费者模型
// 使用条件变量实现一个生产者消费者模型
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <thread>
class TaskQueue {
private:
int capacity; // 任务队列的容量
std::mutex m; // 独占互斥锁
std::condition_variable_any full; // 阻塞生产者线程的条件变量
std::condition_variable_any empty; // 阻塞消费者线程的条件变量
std::queue<int> m_queue; // 存储任务的队列,任务为int型数据
public:
TaskQueue(int capacity) : capacity(capacity) {}
void put(const int& x) {
std::lock_guard<std::mutex> locker(m);
full.wait(m, [this]() {
return this->m_queue.size() != this->capacity;
});
this->m_queue.push(x);
std::cout << "生产者线程id:" << std::this_thread::get_id() << ",put x:" << x << std::endl;
empty.notify_all();
}
void take() {
m.lock();
empty.wait(m, [this]() {
return this->m_queue.size() != 0;
});
int x = this->m_queue.front();
m_queue.pop();
std::cout << "消费者线程id:" << std::this_thread::get_id() << ",take x:" << x << std::endl;
m.unlock();
full.notify_all();
}
// 获取当前任务队列的大小
int size() {
std::lock_guard<std::mutex> locker(m);
return this->m_queue.size();
}
// 判断是否任务队列为空
bool isEmpty() {
std::lock_guard<std::mutex> locker(m);
return m_queue.empty();
}
// 判断任务队列是否为满
bool isFull() {
std::lock_guard<std::mutex> locker(m);
return m_queue.size() == this->capacity;
}
};
int main() {
TaskQueue task(100);
// 生产者线程、消费者线程各5个
std::thread producer[5], consume[5];
for (int i = 0; i < 5; ++i) {
producer[i] = std::thread(&TaskQueue::put, &task, i + 100);
consume[i] = std::thread(&TaskQueue::take, &task);
}
for (int i = 0; i < 5; ++i) {
producer[i].join();
consume[i].join();
}
return 0;
}
3.两种互斥量的优缺点
- condition_variable只能和独占的非递归互斥锁mutex配合使用,有一定的局限性
- condition_variable_any可以和多种互斥锁使用,应用场景更广
线程同步之原子变量
c++11提供了一个原子类型std::atomic
1.atomic类的成员
- 修改或者设置原子变量的值,这是原子操作
// 1.使用重载的赋值运算符
operator=(_Tp __i)
// 2. 使用store方法
store(_Tp __i, memory_order __m = memory_order_seq_cst)
函数参数:
i:存储到原子变量中的值
order:强制的内存次序
- 获取原子变量的值,这是原子操作
_Tp load(memory_order __m = memory_order_seq_cst)
2.类型别名
c++11中提供了很多类型别名,方便定义原子类型,例如:
/// atomic_bool
typedef atomic<bool> atomic_bool;
/// atomic_char
typedef atomic<char> atomic_char;
3.内存顺序约束
- 定义如下:
typedef enum memory_order
{
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst // 默认
} memory_order;
- 解释如下:
- release:表示将数据写入到内存
- acquire:表示从内存读取数据
- memory_order_consume:是memory_order_acquire的加强版,它开销更小
- memory_order_seq_cst:是memory_order_rel的加强版
4.示例
- 使用了原子变量,可以代替使用独占互斥锁。示例如下
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
class Counter {
public:
void add() {
for (int i = 0; i < 5; ++i) {
m_value.fetch_add(1);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void sub() {
for (int i = 0; i < 5; ++i) {
m_value.fetch_add(-1);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
int get()const {
return m_value.load();
}
private:
std::atomic_int m_value{0};
};
int main() {
Counter counter;
std::thread t1(&Counter::add, &counter);
std::thread t2(&Counter::sub, &counter);
t1.join();
t2.join();
// 0
std::cout << counter.get() << std::endl;
return 0;
}
线程同步之信号量
使用c语言已有的信号量完成线程同步
线程异步
1.std::future
- 在多线程程序中的任务大都是异步的,主线程和子线程分别执行不同的任务。如果想要在主线程中获取某个子线程任务函数返回的结果可以使用C++11提供的future类。这个类需要和其它类或者函数搭配使用。
- future类的方法:
- 获取future对象内部保存的数据:get方法,这是一个阻塞函数。
- wait:阻塞当前的线程
- wait_for:线程阻塞一定的时长
- wait_until:线程阻塞到某一指定的时间点
wait_until和wait_for的返回值 /// Status code for futures enum class future_status { ready, // 子线程中的任务已经执行完毕,结果已就绪 timeout,// 子线程中的任务正在执行中,指定等待时长已用完 deferred// 子线程中的任务函仍未启动 };
2.std::promise
- std::promise 是一个协助线程赋值的类,它能够将数据和future对象绑定起来,为获取线程函数中的某个值提供便利。
- 常用方法
- get_future:获取future对象
- set_value:存储要传出的value值
- set_value_at_thread_exit:存储要传出的value值,但是不立即令状态就绪。在当前线程退出时,子线程资源被销毁,再令状态就绪。
- 通过promise传递数据的过程如下:
- 在主线程中创建 std::promise 对象
- 将这个 std::promise 对象通过引用的方式传递给子线程的任务函数
- 在子线程任务函数中给 std::promise 对象赋值
- 在主线程中通过 std::promise 对象取出绑定的 future 实例对象
- 通过得到的 future 对象取出子线程任务函数中返回的值。
- 示例:
- 在子线程任务函数执行期间,让状态就绪
#include <iostream> #include <future> #include <thread> int main() { std::promise<int> pr; std::thread t1([](std::promise<int>& p){ p.set_value(1024); std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "waiting 3 seconds" << std::endl; }, std::ref(pr)); std::future<int>f = pr.get_future(); int ret = f.get(); std::cout << "value:" << ret << std::endl; t1.join(); return 0; } // 输出结果 value:1024 waiting 3 seconds
- 子线程任务函数执行结束,让状态就绪
#include <iostream> #include <future> #include <thread> int main() { std::promise<int> pr; std::thread t1([](std::promise<int>& p){ p.set_value_at_thread_exit(1024); std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "waiting 3 seconds" << std::endl; }, std::ref(pr)); std::future<int>f = pr.get_future(); int ret = f.get(); std::cout << "value:" << ret << std::endl; t1.join(); return 0; } // 输出结果 waiting 3 seconds value:1024
3.std::packaged_task
- 这个类可以将内部包装的函数和future类绑定到一起,以便进行后续的异步调用,它和 std::promise 有点类似,std::promise内部保存一个共享状态的值,而 std::packaged_task 保存的是一个函数。
- 常用方法:
- get_future:获取future对象
- 示例:
#include <iostream>
#include <thread>
#include <future>
using namespace std;
int main()
{
packaged_task<int(int)> task([](int x) {
return x += 1024;
});
thread t1(ref(task), 0);
future<int> f = task.get_future();
int value = f.get();
cout << "value: " << value << endl;
t1.join();
return 0;
}
4.std::async
- async是一个函数模板,在c++11中有两种调用方式:
- 方式1:直接调用传递到函数体内部的可调用对象,返回一个 future 对象
future<__async_result_of<_Fn, _Args...>> async(_Fn&& __fn, _Args&&... __args);
- 方式2:通过指定的策略调用传递到函数内部的可调用对象,返回一个 future 对象
future<__async_result_of<_Fn, _Args...>> async(launch __policy, _Fn&& __fn, _Args&&... __args); 函数参数: policy:调用对象fn的执行策略 std::launch::async:表示调用async函数时创建新的线程执行任务函数 std::launch::deferred:调用async函数时不执行任务函数,直到调用了 future 的 get() 或者 wait() 时才执行任务。 这种方式不会创建新的线程
线程局部存储TLS
- C++ 11标准提供了一个新的关键字thread_local来定义一个线程局部变量。
- 示例如下:
#include <iostream>
#include <thread>
// 定义线程局部变量
thread_local int g_number;
void read() {
while (true) {
std::cout << "g_number:" << g_number << "," << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
void write() {
while (true) {
g_number++;
std::cout << "g_number:" << g_number << "," << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main() {
std::thread t1(read);
std::thread t2(write);
if (t1.joinable()) t1.join();
if (t2.joinable()) t2.join();
return 0;
}