C++11线程

在 C++11 之前,C++没有对线程提供语言级别的支持,各种操作系统和编译器实现线程的方法不
一样。
C++11 增加了线程以及线程相关的类,统一编程风格、简单易用、跨平台。

一、创建线程,线程资源回收

头文件:#include <thread>
线程类:std::thread

构造函数:
1)thread() noexcept;
默认构造函数,构造一个线程对象,不执行任何任务(不会创建/启动子线程)。
2)template< class Function, class... Args >
explicit thread(Function&& fx, Args&&... args );

创建线程对象,在线程中执行任务函数 fx 中的代码,args 是要传递给任务函数 fx 的参数。
任务函数 fx 可以是普通函数、类的非静态成员函数、类的静态成员函数、lambda 函数、仿函数。
3)thread(const thread& ) = delete;
删除拷贝构造函数,不允许线程对象之间的拷贝。
4)thread(thread&& other ) noexcept;

线程中的资源不能被复制,如果 other 是右值,会进行资源所有权的转移,如果 other 是左值,禁
止拷贝。
注意:
先创建的子线程不一定跑得最快(程序运行的速度有很大的偶然性)。

 线程的任务函数返回后,子线程将终止。

 如果主程序(主线程)退出(不论是正常退出还是意外终止),全部的子线程将强行被终止。

#include <iostream>
#include <thread> // 线程类头文件。
#include <windows.h> // Sleep()函数需要这个头文件。
using namespace std;
// 普通函数。
void func(int bh, const string& str) {
for (int ii = 1; ii <= 10; ii++)
{
cout<<bh << " " << str << endl;
Sleep(1000); // 休眠 1 秒。
}
}
// 仿函数。
class mythread1
{
public:
void operator()(int bh, const string& str) {
for (int ii = 1; ii <= 10; ii++)
{
cout<<bh << " " << str << endl;
Sleep(1000); // 休眠 1 秒。
}
}
};
// 类中有静态成员函数。
class mythread2
{
public:
static void func(int bh, const string& str) {
for (int ii = 1; ii <= 10; ii++)
{
cout<<bh << " " << str << endl;
}
}
};
// 类中有普通成员函数。
class mythread3
{
public:
void func(int bh, const string& str) {
for (int ii = 1; ii <= 10; ii++)
{
cout<<bh << " " << str << endl;
Sleep(1000); // 休眠 1 秒。
}
}
};
int main()
{
// 用普通函数创建线程。
//thread t1(func, 3, "test");
//thread t2(func, 8, "test");
// 用 lambda 函数创建线程。
auto f = [](int bh, const string& str) {
for (int ii = 1; ii <= 10; ii++)
{
cout<<bh << " " << str << endl;
Sleep(1000); // 休眠 1 秒。
}
};
//thread t3(f, 3, "test");
// 用仿函数创建线程。
//thread t4(mythread1(), 3, "test");
// 用类的静态成员函数创建线程。
//thread t5(mythread2::func, 3, "test");
// 用类的普通成员函数创建线程。
mythread3 myth; // 必须先创建类的对象,必须保证对象的生命周期比子线程要长。
thread t6(&mythread3::func, &myth, 3, "test"); // 第二个参数必须填对象的
//this 指针,否则会拷贝对象。
cout << "任务开始。\n";
for (int ii = 0; ii < 10; ii++) {
cout << "执行任务中......\n";
Sleep(1000); // 假设执行任务需要时间。
}
cout << "任务完成。\n";
//t1.join(); // 回收线程 t1 的资源。
//t2.join(); // 回收线程 t2 的资源。
//t3.join(); // 回收线程 t3 的资源。
//t4.join(); // 回收线程 t4 的资源。
//t5.join(); // 回收线程 t5 的资源。
t6.join(); // 回收线程 t6 的资源。

虽然同一个进程的多个线程共享进程的栈空间,但是,每个子线程在这个栈中拥有自己私有的栈空间。所
以,线程结束时需要回收资源。
回收子线程的资源有两种方法:
1)在主程序中,调用 join()成员函数等待子线程退出,回收它的资源。如果子线程已退出,join()
函数立即返回,否则会阻塞等待,直到子线程退出。
2)在主程序中,调用 detach()成员函数分离子线程,子线程退出时,系统将自动回收资源。分离
后的子线程不可 join()
3)用 joinable()成员函数可以判断子线程的分离状态,函数返回布尔类型。

#include <iostream>
#include <thread> // 线程类头文件。
#include <windows.h> // Sleep()函数需要这个头文件。
using namespace std;
// 普通函数。
void func(int bh, const string& str) {
for (int ii = 1; ii <= 10; ii++)
{
cout<< bh << "  " << str << endl;
Sleep(1000); // 休眠 1 秒。
}
}
int main()
{
// 用普通函数创建线程。
thread t1(func, 3, "test");
thread t2(func, 8, "test");
t1.detach(); t2.detach(); // 分离子线程。
//cout << "任务开始。\n";
//for (int ii = 0; ii < 12; ii++) {
// cout << "执行任务中......\n";
// Sleep(1000); // 假设执行任务需要时间。
//}
//cout << "任务完成。\n";
//t1.join(); // 回收线程 t1 的资源。
//t2.join(); // 回收线程 t2 的资源。
Sleep(12000);
}

二、this_thread 的全局函数

C++11 提供了命名空间 this_thread 来表示当前线程,该命名空间中有四个函数:get_id()、sleep
_for()、sleep_until()、yield()。

1)get_id()

thread::id get_id() noexcept;
该函数用于获取线程 ID,thread 类也有同名的成员函数。

2)sleep_for()  Linux sleep(1)

template <class Rep, class Period>
void sleep_for (const chrono::duration<Rep,Period>& rel_time);

该函数让线程休眠一段时间。

3)sleep_until() 

template <class Clock, class Duration>
void sleep_until (const chrono::time_point<Clock,Duration>& abs_time);

该函数让线程休眠至指定时间点。(可实现定时任务)

4)yield()

void yield() noexcept;
该函数让线程主动让出自己已经抢到的 CPU 时间片。


5)thread 类其它的成员函数

void swap(std::thread& other); // 交换两个线程对象。
static unsigned hardware_concurrency() noexcept; // 返回硬件线程上下文的数量。
The interpretation of this value is system- andimplementation- specific, and may not
be exact, but just an approximation.
Note that this does not need to match the actualnumber of processors or cores avail
able in the system: A system can supportmultiple threads per processing unit, or restrict
the access to its resourcesto the program.
If this value is not computable or well defined,the function returns 0.

#include <iostream>
#include <thread> // 线程类头文件。
using namespace std;
// 普通函数。
void func(int bh, const string& str) {
cout << "子线程:" << this_thread::get_id() << endl;
for (int ii = 1; ii <= 3; ii++)
{
cout<< bh << "  " << str << endl;
this_thread::sleep_for(chrono::seconds(1)); // 休眠 1 秒。
}
}
int main()
{
// 用普通函数创建线程。
thread t1(func, 3, "test");
thread t2(func, 8, "test");
cout << "主线程:" << this_thread::get_id() << endl;
cout << "线程 t1:" << t1.get_id() << endl;
cout << "线程 t2:" << t2.get_id() << endl;
t1.join(); // 回收线程 t1 的资源。
t2.join(); // 回收线程 t2 的资源。

三、call_once函数

在多线程环境中,某些函数只能被调用一次,例如:初始化某个对象,而这个对象只能被初始化一
次。
在线程的任务函数中,可以用 std::call_once()来保证某个函数只被调用一次。
头文件:#include <mutex>
template< class callable, class... Args >
void call_once( std::once_flag& flag, Function&& fx, Args&&... args );

第一个参数是 std::once_flag,用于标记函数 fx 是否已经被执行过。
第二个参数是需要执行的函数 fx。

后面的可变参数是传递给函数 fx 的参数。

#include <iostream>
#include <thread> // 线程类头文件。
#include <mutex> // std::once_flag 和 std::call_once()函数需要包含这个头文件。
using namespace std;
once_flag onceflag; // once_flag 全局变量。本质是取值为 0 和 1 的锁。
// 在线程中,打算只调用一次的函数。
void once_func(const int bh, const string& str) {
cout << "once_func() bh= " << bh << ", str=" << str << endl;
}
// 普通函数。
void func(int bh, const string& str) {
call_once(onceflag,once_func,0, "test");
for (int ii = 1; ii <= 3; ii++)
{
cout<< bh << "  " << str << endl;
this_thread::sleep_for(chrono::seconds(1)); // 休眠 1 秒。
}
}
int main()
{
// 用普通函数创建线程。
thread t1(func, 3, "test");
thread t2(func, 8, "test");
t1.join(); // 回收线程 t1 的资源。
t2.join(); // 回收线程 t2 的资源。
}

四、native_handle函数

C++11 定义了线程标准,不同的平台和编译器在实现的时候,本质上都是对操作系统的线程库进行封装,会损失一部分功能。
为了弥补 C++11 线程库的不足,thread 类提供了 native_handle()成员函数,用于获得与操作系
统相关的原生线程句柄,操作系统原生的线程库就可以用原生线程句柄操作线程。

#include <iostream>
#include <thread>
#include <pthread.h> // Linux 的 pthread 线程库头文件。
using namespace std;
void func() // 线程任务函数。
{
for (int ii=1;ii<=10;ii++)
{
cout << "ii=" << ii << endl;
this_thread::sleep_for(chrono::seconds(1)); // 休眠 1 秒。
}
}
int main()
{
thread tt(func); // 创建线程。
this_thread::sleep_for(chrono::seconds(5)); // 休眠 5 秒。
pthread_t thid= tt.native_handle(); // 获取 Linux 操作系统原生的线程句柄。
pthread_cancel(thid); // 取消线程。
tt.join(); // 等待线程退出。
}

五、互斥锁

C++11 提供了四种互斥锁:
1) mutex:互斥锁。
2) timed_mutex:带超时机制的互斥锁。
3) recursive_mutex:递归互斥锁。
4) recursive_timed_mutex:带超时机制的递归互斥锁。

包含头文件:#include <mutex>

一、mutex类

1)加锁 lock()
互斥锁有锁定和未锁定两种状态。
如果互斥锁是未锁定状态,调用 lock()成员函数的线程会得到互斥锁的所有权,并将其上锁。如果互斥锁是锁定状态,调用 lock()成员函数的线程就会阻塞等待,直到互斥锁变成未锁定状态。

2)解锁 unlock()
只有持有锁的线程才能解锁

3)尝试加锁 try_lock()
如果互斥锁是未锁定状态,则加锁成功,函数返回 true。
如果互斥锁是锁定状态,则加锁失败,函数立即返回 false。(线程不会阻塞等待)

#include <iostream>
#include <thread> // 线程类头文件。
#include <mutex> // 互斥锁类的头文件。
using namespace std;
mutex mtx; // 创建互斥锁,保护共享资源 cout 对象。
// 普通函数。
void func(int bh, const string& str) {
for (int ii = 1; ii <= 10; ii++)
{
mtx.lock(); // 申请加锁。
cout<< bh << "  " << str << endl;
mtx.unlock(); // 解锁。
this_thread::sleep_for(chrono::seconds(1)); // 休眠 1 秒。
}
}
int main()
{
// 用普通函数创建线程。
thread t1(func, 1, "test");
thread t2(func, 2, "test");
thread t3(func, 3, "test");
thread t4(func, 4, "test");
thread t5(func, 5, "test");
t1.join(); // 回收线程 t1 的资源。
t2.join(); // 回收线程 t2 的资源。
t3.join(); // 回收线程 t3 的资源。
t4.join(); // 回收线程 t4 的资源。
t5.join(); // 回收线程 t5 的资源。
}

二、timed_mutex 类

增加了两个成员函数:
bool try_lock_for(时间长度);
bool try_lock_until(时间点);

三、recursive_mutex 类

递归互斥锁允许同一线程多次获得互斥锁,可以解决同一线程多次加锁造成的死锁问题。

#include <iostream>
#include <mutex> // 互斥锁类的头文件。
using namespace std;
class AA
{
recursive_mutex m_mutex;
public:
void func1() {
m_mutex.lock();
cout << "调用了 func1()\n";
m_mutex.unlock();
}
void func2() {
m_mutex.lock();
cout << "调用了 func2()\n";
func1();
m_mutex.unlock();
}
};
int main()
{
AA aa;
//aa.func1();
aa.func2();
}

四、lock_guard类

lock_guard 是模板类,可以简化互斥锁的使用,也更安全。
lock_guard 的定义如下:
template<class Mutex>
class lock_guard
{
explicit lock_guard(Mutex& mtx);
}
lock_guard 在构造函数中加锁,在析构函数中解锁。
lock_guard 采用了 RAII 思想(在类构造函数中分配资源,在析构函数中释放资源,保证资源在离
开作用域时自动释放)。

六、条件变量—生产消费者模型

条件变量是一种线程同步机制。当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些
线程才会被唤醒。

C++11 的条件变量提供了两个类:
condition_variable:只支持与普通 mutex 搭配,效率更高。
condition_variable_any:是一种通用的条件变量,可以与任意 mutex 搭配(包括用户自定义的锁
类型)。
包含头文件:<condition_variable>

一、condition_variable 类

主要成员函数:
1)condition_variable() 默认构造函数。
2)condition_variable(const condition_variable &)=delete 禁止拷贝。
3)condition_variable& condition_variable::operator=(const condition_variable &)=delete
禁止赋值。
4)notify_one() 通知一个等待的线程。
5)notify_all() 通知全部等待的线程。
6)wait(unique_lock<mutex> lock) 阻塞当前线程,直到通知到达。
7)wait(unique_lock<mutex> lock,Pred pred) 循环的阻塞当前线程,直到通知到达且谓词满
足。
8)wait_for(unique_lock<mutex> lock,时间长度)
9)wait_for(unique_lock<mutex> lock,时间长度,Pred pred)
10)wait_until(unique_lock<mutex> lock,时间点)
11)wait_until(unique_lock<mutex> lock,时间点,Pred pred)

二、unique_lock 类

template <class Mutex> class unique_lock 是模板类,模板参数为互斥锁类型。
unique_lock 和 lock_guard 都是管理锁的辅助类,都是 RAII 风格(在构造时获得锁,在析构时释
放锁)。它们的区别在于:为了配合 condition_variable,unique_lock 还有 lock()和 unlock()成员函数。

#include <iostream>
#include <string>
#include <thread> // 线程类头文件。
#include <mutex> // 互斥锁类的头文件。
#include <deque> // deque 容器的头文件。
#include <queue> // queue 容器的头文件。
#include <condition_variable> // 条件变量的头文件。
using namespace std;
class AA
{
mutex m_mutex; // 互斥锁。
condition_variable m_cond; // 条件变量。
queue<string, deque<string>> m_q; // 缓存队列,底层容器用 deque。
public:
void incache(int num) // 生产数据,num 指定数据的个数。
{
lock_guard<mutex> lock(m_mutex); // 申请加锁。
for (int ii=0 ; ii<num ; ii++)
{
static int bh = 1; 
string message = to_string(bh++) + "号"; // 拼接出一个数据。
m_q.push(message); // 把生产出来的数据入队。
}
m_cond.notify_one(); // 唤醒一个被当前条件变量阻塞的线程。
}
void outcache() // 消费者线程任务函数。
{
while (true)
{
string message;
{
// 把互斥锁转换成 unique_lock<mutex>,并申请加锁。
unique_lock<mutex> lock(m_mutex);
while (m_q.empty()) // 如果队列空,进入循环,否则直接处理数据。必须用
循环,不能用 if
m_cond.wait(lock); // 等待生产者的唤醒信号。
// 数据元素出队。
message = m_q.front(); m_q.pop();
}
// 处理出队的数据(把数据消费掉)。
this_thread::sleep_for(chrono::milliseconds(1)); // 假设处理数据需要 1 毫秒。
cout << "线程:" << this_thread::get_id() << "," << message << endl;
}
}
};
int main()
{
AA aa;
thread t1(&AA::outcache, &aa); // 创建消费者线程 t1。
thread t2(&AA::outcache, &aa); // 创建消费者线程 t2。
thread t3(&AA::outcache, &aa); // 创建消费者线程 t3。
this_thread::sleep_for(chrono::seconds(2)); // 休眠 2 秒。
aa.incache(3); // 生产 3 个数据。
this_thread::sleep_for(chrono::seconds(3)); // 休眠 3 秒。
aa.incache(5); // 生产 5 个数据。
t1.join(); // 回收子线程的资源。
t2.join();
t3.join();
#include <iostream>
#include <string>
#include <thread> // 线程类头文件。
#include <mutex> // 互斥锁类的头文件。
#include <deque> // deque 容器的头文件。
#include <queue> // queue 容器的头文件。
#include <condition_variable> // 条件变量的头文件。
using namespace std;
class AA
{
mutex m_mutex; // 互斥锁。
condition_variable m_cond; // 条件变量。
queue<string, deque<string>> m_q; // 缓存队列,底层容器用 deque。
public:
void incache(int num) // 生产数据,num 指定数据的个数。
{
lock_guard<mutex> lock(m_mutex); // 申请加锁。
for (int ii=0 ; ii<num ; ii++)
{
static int bh = 1;。
string message = to_string(bh++) + "号"; // 拼接出一个数据。
m_q.push(message); // 把生产出来的数据入队。
}
//m_cond.notify_one(); // 唤醒一个被当前条件变量阻塞的线程。
m_cond.notify_all(); // 唤醒全部被当前条件变量阻塞的线程。
}
void outcache() { // 消费者线程任务函数。
while (true) {
// 把互斥锁转换成 unique_lock<mutex>,并申请加锁。
unique_lock<mutex> lock(m_mutex);
// 条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据。
//while (m_q.empty()) // 如果队列空,进入循环,否则直接处理数据。必须用循
环,不能用 if
// m_cond.wait(lock); // 1)把互斥锁解开;2)阻塞,等待被唤醒;3)给互斥
锁加锁。
m_cond.wait(lock, [this] { return !m_q.empty(); });
// 数据元素出队。
string message = m_q.front(); m_q.pop();
cout << "线程:" << this_thread::get_id() << "," << message << endl;
lock.unlock(); // 手工解锁。
// 处理出队的数据(把数据消费掉)。
this_thread::sleep_for(chrono::milliseconds(1)); // 假设处理数据需要 1 毫秒。
}
}
};
int main()
{
AA aa;
thread t1(&AA::outcache, &aa); // 创建消费者线程 t1。
thread t2(&AA::outcache, &aa); // 创建消费者线程 t2。
thread t3(&AA::outcache, &aa); // 创建消费者线程 t3。
this_thread::sleep_for(chrono::seconds(2)); // 休眠 2 秒。
aa.incache(2); // 生产 2 个数据。
this_thread::sleep_for(chrono::seconds(3)); // 休眠 3 秒。
aa.incache(5); // 生产 5 个数据。
t1.join(); // 回收子线程的资源。
t2.join();
t3.join();
}

七、原子类型 atomic

C++11 提供了 atomic<T>模板类(结构体),用于支持原子类型,模板参数可以是 bool、char、
int、long、long long、指针类型(不支持浮点类型和自定义数据类型)。
原子操作由 CPU 指令提供支持,它的性能比锁和消息传递更高,并且,不需要程序员处理加锁和释放锁的问题,支持修改、读取、交换、比较并交换等操作。
头文件:#include <atomic>
构造函数:
atomic() noexcept = default; // 默认构造函数。
atomic(T val) noexcept; // 转换函数。
atomic(const atomic&) = delete; // 禁用拷贝构造函数。

赋值函数:
atomic& operator=(const atomic&) = delete; // 禁用赋值函数。
常用函数:
void store(const T val) noexcept; // 把 val 的值存入原子变量。
T load() noexcept; // 读取原子变量的值。
T fetch_add(const T val) noexcept; // 把原子变量的值与 val 相加,返回原值。
T fetch_sub(const T val) noexcept; // 把原子变量的值减 val,返回原值。
T exchange(const T val) noexcept; // 把 val 的值存入原子变量,返回原值。
T compare_exchange_strong(T &expect,const T val) noexcept; // 比较原子变量的值和预期值expect,如果当两个值相等,把 val 存储到原子变量中,函数返回 true;如果当两个值不相等,用原子变量的值更新预期值,函数返回 false。CAS 指令。

bool is_lock_free(); // 查询某原子类型的操作是直接用 CPU 指令(返回 true),还是编译器内
部的锁(返回 false)。

原子类型的别名:

注意:
1) atomic<T>模板类重载了整数操作的各种运算符。
2)atomic<T>模板类的模板参数支持指针,但不表示它所指向的对象是原子类型。
3)原子整型可以用作计数器,布尔型可以用作开关。
4)CAS 指令是实现无锁队列基础。

#include <iostream>
#include <atomic> // 原子类型的头文件。
using namespace std;
int main()
{
atomic<int> a = 3; // atomic(T val) noexcept; // 转换函数。
cout << "a=" << a.load() << endl; // 读取原子变量 a 的值。输出:a=3
a.store(8); // 把 8 存储到原子变量中。
cout << "a=" << a.load() << endl; // 读取原子变量 a 的值。 输出:a=8
int old; // 用于存放原值。
old = a.fetch_add(5); // 把原子变量 a 的值与 5 相加,返回原值。
cout << "old = " << old <<",a = " << a.load() << endl; // 输出:old=8,a=13
old = a.fetch_sub(2); // 把原子变量 a 的值减 2,返回原值。
cout << "old = " << old << ",a = " << a.load() << endl; // 输出:old=13,a=11
atomic<int> ii = 3; // 原子变量
int expect = 4; // 期待值
int val = 5; // 打算存入原子变量的值
// 比较原子变量的值和预期值 expect,
// 如果当两个值相等,把 val 存储到原子变量中;
// 如果当两个值不相等,用原子变量的值更新预期值。
// 执行存储操作时返回 true,否则返回 false。
bool bret = ii.compare_exchange_strong(expect, val);
cout << "bret=" << bret << endl;
cout << "ii=" << ii << endl;
cout << "expect=" << expect << endl;
}

八、End.

相关推荐

  1. C++11 Thead线线

    2024-02-02 21:14:01       17 阅读
  2. C/C++ 16C++11线

    2024-02-02 21:14:01       31 阅读
  3. c++11线库的使用】

    2024-02-02 21:14:01       15 阅读
  4. C++11线基本知识点

    2024-02-02 21:14:01       31 阅读
  5. C++11 Thead线库的基本使用

    2024-02-02 21:14:01       33 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-02 21:14:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-02 21:14:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-02 21:14:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-02 21:14:01       18 阅读

热门阅读

  1. 02-02

    2024-02-02 21:14:01       28 阅读
  2. C++重新入门-C++简介

    2024-02-02 21:14:01       37 阅读
  3. [leetcode] 22. 括号生成

    2024-02-02 21:14:01       38 阅读
  4. 【C语言】(13)堆和栈

    2024-02-02 21:14:01       32 阅读
  5. Unity2D_单向平台

    2024-02-02 21:14:01       33 阅读
  6. Redis的big key问题介绍以及监控手段

    2024-02-02 21:14:01       25 阅读
  7. 每日学习-2月1日

    2024-02-02 21:14:01       26 阅读