暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

C++11 语言特性和标准库 并发

林元皓 2021-08-06
375

C++11标准库:threads 线程

threas :C++11增加了一个线程库, 用来创建线程, 多线程在项目开发中能提高CPU利用率,是在操作系统中非常常见的功能.。 目前还没有线程管理的类, 需要自己实现, 比如线程池。

threads.cpp

#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <chrono>
#include <functional>


using namespace std;


/*
1、语法说明
2、使用说明
3、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << ":" << s << std::endl;
#define TLOG(t,s) std::cout << t << ":" << s << std::endl;




class OneClass
{
public:
std::string str_;
OneClass(const char* str) :str_(str) { }


void operator()() {
LOG(str_);
}
};


void OneFunc(std::string str)
{
TLOG("OneFunc", str);
if (this_thread::get_id() == thread::id()) {
PRINT("get_id() == id()");
}
}


void FuncNativeHandle(int seconds1)
{
this_thread::sleep_for(chrono::seconds(seconds1));
PRINT("FuncNativeHandle Finish");
}


void FuncYield(std::string str)
{
TLOG("FuncYield begin", str);
//1. 让渡,降低自己的优先级,让别的线程有机会调用,也就是建议CPU分配资源给其他线程。
//2. 注意在多线程个数超出cpu核心的个数有效.
//3. 通常情况下用在监听事件里,比如判断某些消息没传来,不着急循环调用,先调用yield()能让当前线程让渡CPU资源,比sleep更能提高性能.
int count = 0;
for (int i = 0; i < 10000; ++i) {
count += (i % 5);
this_thread::yield();
}
TLOG("FuncYield end", str);
}


void FuncNoYield(std::string str)
{
TLOG("FuncNoYield begin", str);
int count = 0;
for (int i = 0; i < 10000; ++i) {
count += (i % 5);
}
TLOG("FuncNoYield end", str);
}


void CreateJoinableThreads()
{
PRINT("================= CreateJoinableThreads ==============");
// 创建thread; 需绑定函数或函数对象
// 绑定重载了operator()的类
thread t1(OneClass("vic.MINg"));
t1.join();


// 绑定函数
thread t2(OneFunc, "vic.MINg");
t2.join();


// 绑定lambda表达式,如果线程是detach线程
// 传给lambda表达式的外部变量生命周期要是堆内存变量.
thread t3([](const char* str) {
TLOG("Lambda", str);
}, "vic.MINg");
t3.join();


// 绑定 std::function
thread t4(bind(OneFunc, "vic.MINg"));
t4.join();


thread t5; // 未启动线程,无用,C++11没有提供后续的启动线程方法.
LOG(t5.joinable());


thread t6(OneFunc, "Join");
TLOG("t6 join() before", t6.joinable());


t6.join(); // 调用join()之后也是不可joinable();
TLOG("t6 join() after", t6.joinable());


thread t7(OneFunc, "Detach");
t7.detach(); // 调用了detach之后是不可joinable的.
TLOG("t7 detach(): ", t6.joinable());
}


void ThreadOtherFunc()
{
PRINT("================= ThreadOtherFunc ==============");
// 获取最佳的并行线程个数, 即CPU核心个数.
auto number = thread::hardware_concurrency();
LOG(number);


// 获取线程ID的散列码
hash<thread::id> hasher;
auto _id = hasher(this_thread::get_id());
TLOG("Thread hash ID(size_t)", _id);


// sleep_util, 参数是time_point, 直到某个时刻.
// 当前时间往后推2秒
auto begin = chrono::steady_clock::now();
auto tti = time(NULL);
tm tm1;
#ifdef _WIN32
localtime_s(&tm1, &tti);
#else
tm1 = *localtime(&tti);
#endif


tm1.tm_sec += 2;
auto tti2 = mktime(&tm1);
auto ti = chrono::system_clock::from_time_t(tti2); // 换算需要耗费时间
this_thread::sleep_until(ti);
auto offset = chrono::steady_clock::now() - begin;
auto millseconds1 = chrono::duration_cast<chrono::milliseconds>(offset);
TLOG("sleep_until millseconds", millseconds1.count());


// 获取底层实现的本地线程句柄,比如Windows VS2017的返回 HANDLE,gcc返回的是pthread句柄.
thread t1(FuncNativeHandle, 5);
auto handle = t1.native_handle();
TLOG("handle", handle);
t1.join();
}


void ThreadYield()
{
PRINT("================= ThreadYield ==============");
std::vector<thread*> args;
for (int i = 0; i < 10; ++i) {
thread* t1 = NULL;
if (i % 5)
t1 = new thread(FuncNoYield, "ThreadYield" + to_string((long long)i));
else
t1 = new thread(FuncYield, "ThreadYield" + to_string((long long)i));


args.push_back(t1);
}


for (int i = 0; i < args.size(); ++i)
(*args[i]).join();
}


void CreateDetachThreads()
{
PRINT("================= CreateDetachThreads ==============");
thread t1([]() {
std::this_thread::sleep_for(std::chrono::seconds(2));
PRINT("Detach Thread");
});
t1.detach();
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


int main(int argc, char const *argv[])
{
std::cout << "---------- 1、语法说明 ---------------" << std::endl;
// C++11 的 thread 线程类如果要激活使用必须在创建时传入线程处理函数或函数对象,无参的默认构造函数目前没有什么用, 目前 thread 类并没有 operator() 的重载支持后续添加处理函数。
// 注意, thread类禁用了复制构造函数。


std::cout << "---------- 2、使用说明 ---------------" << std::endl;
// 1、 线程是程序执行/计算的表示。在 C++11 中,就像在许多现代计算中一样,一个线程可以(通常也确实)与其他线程共享一个地址空间。
// 在这一点上,它与进程不同,后者通常不直接与其他进程共享数据。
// 在过去,C++ 已经为各种硬件和操作系统提供了大量的线程实现,现在的新特性是标准库线程库。


// 2、C++ 的委员会 POSIX 代表强烈反对任何形式的“线程取消”,原因是大多数的 C++ 的资源模型依赖于析构函数。对于每个系统和每个可能的应用程序,都没有完美的解决方案。


// 3、C++11的线程和大多数的平台的 C/C++ 实现是一样的,有两种类别的线程, joinable 和 detach 线程,即可加入等待的线程,可分离的线程;
// 可 joinable 的线程当调用线程的方法 join(), 可在主线程里等待工作线程结束再执行下一条语句;
// 可分离的线程调用 detach() 方法,之后即便 thread 实例对象销毁,线程处理函数一样继续执行(底层线程执行内核句柄并没销毁)。


// 4、线程的基本问题是数据竞争;也就是说,在一个地址空间中运行的两个线程可以以导致未定义结果的方式独立地访问一个对象。
// 如果一个(或两个)写对象,而另一个(或两个)读对象,他们有一个“竞赛”谁先完成它的操作。
// 结果不仅没有定义; 它们通常是完全不可预测的。因此,C++11 为程序员提供了一些规则 / 保证来避免数据竞争:
// -- C++ 标准库函数不能直接或间接地访问当前线程以外的线程可以访问的对象,除非这些对象是通过函数的参数直接或间接地访问的,包括 this;
// -- C++ 标准库函数不能直接或间接地修改当前线程以外的线程可以访问的对象,除非这些对象是通过函数的非常量参数直接或间接地访问的,包括this。
// -- 当同时修改同一序列中的不同元素时,需要使用 C++ 标准库实现来避免数据竞争。
// -- 除非另有说明,否则多线程并发访问流对象、流缓冲区对象或 C库流可能导致数据竞争。所以不要在两个线程之间共享输出流,除非您能够控制对它的访问。


std::cout << "---------- 3、使用场景 ---------------" << std::endl;


CreateJoinableThreads();
ThreadOtherFunc();
ThreadYield();
CreateDetachThreads();


system("pause");


return 0;
}

C++11标准库:mutual-exclusion 互斥量

mutual-exclusion :在多线程编程中,访问共享变量的一种方案就是使用互斥量,C++11 在 库里提供了多种类型的互斥量,用好可以防止共享变量被并发访问导致崩溃。

mutual-exclusion.cpp

#include <algorithm>
#include <vector>
#include <iostream>
#include <random>
#include <string>
#include <assert.h>
#include <memory>
#include <thread>
#include <chrono>
#include <functional>
#include <mutex>
using namespace std;


/*
1、语法说明
2、使用说明
3、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << ":" << s << std::endl;
#define TLOG(t,s) std::cout << t << ":" << s << std::endl;


static mutex m;
void MutexLock(int count, int loopTimes)
{
PRINT("================= MutexLock ==============");
auto args = new std::vector<int>();
shared_ptr<std::vector<int>> sp_args(args);
auto threads = new std::vector<thread*>();
shared_ptr<std::vector<thread*>> sp_threads(threads);


for (int i = 0; i < count; ++i) {
auto t1 = new thread([&args](mutex *m, int loopTimes) {


for (int j = 0; j < loopTimes; ++j) {
m->lock();
args->push_back(j);
m->unlock();
}
hash<thread::id> hasher;
auto _id = hasher(this_thread::get_id());
TLOG("thread finish hash id: ", _id);
}, &m, loopTimes);
threads->push_back(t1);
}


for (auto one : *threads) {
one->join();
delete one;
}


auto size = args->size();
LOG(size);
LOG(count*loopTimes);
LOG((count* loopTimes == size));
}




void MutexTryLock(int count, int loopTimes)
{
PRINT("================= MutexTryLock ==============");
auto args = new std::vector<int>();
shared_ptr<std::vector<int>> sp_args(args);
auto threads = new std::vector<thread*>();
shared_ptr<std::vector<thread*>> sp_threads(threads);


for (int i = 0; i < count; ++i) {
auto t1 = new thread([&args](mutex *m, int loopTimes) {
hash<thread::id> hasher;
auto _id = hasher(this_thread::get_id());
for (int j = 0; j < loopTimes; ++j) {
// 不一定能获取锁,不会等待其他线程解锁,获取不到直接解锁.
if (m->try_lock()) {
args->push_back(j);
this_thread::sleep_for(chrono::milliseconds(10));
m->unlock();
}
}


TLOG("thread finish hash id: ", _id);
}, &m, loopTimes);
threads->push_back(t1);
}


for (auto one : *threads) {
one->join();
delete one;
}


auto size = args->size();
LOG(size);
LOG(count*loopTimes);
LOG((count* loopTimes >= size));
}


int rand_int(int low, int max)
{
static default_random_engine re;
using Dist = uniform_int_distribution<int>;
static Dist dt;
return dt(re, Dist::param_type(low, max));
}


void Func(std::vector<int>* args, std::recursive_mutex* m)
{
m->lock();
args->push_back(rand_int(100, 10000));
m->unlock();
}


void ThreadFunc(std::vector<int>* args, std::recursive_mutex* m)
{
hash<thread::id> hasher;
auto _id = hasher(this_thread::get_id());


m->lock(); // lock和unlock要匹配,不然不会释放锁.
TLOG("BEGIN: ThreadFunc hash id: ", _id);
for (int i = 0; i < 100; ++i) {
args->push_back(i);
Func(args, m);
}
TLOG("END: ThreadFunc hash id: ", _id);
m->unlock();
}




void RecursiveMutex(int count)
{
PRINT("================ RecursiveMutex =================");
// 避免这种递归锁的设计.
recursive_mutex rm;
auto args = new std::vector<int>();
shared_ptr<std::vector<int>> sp_args(args);


auto threads = new std::vector<thread*>();
shared_ptr<std::vector<thread*>> sp_threads(threads);


for (int i = 0; i < count; ++i) {
auto t1 = new thread(ThreadFunc, args, &rm);
threads->push_back(t1);
}


for (auto one : *threads) {
one->join();
delete one;
}


LOG(args->size());
}




void TimeMutex()
{
PRINT("================ TimeMutex =================");
std::timed_mutex m2;
// 最长时间尝试5秒获取锁,超过时间未获取锁就放弃.
if (m2.try_lock_for(chrono::seconds(5))) {
PRINT("try_lock_for");
m2.unlock();
}




// try_lock_util,最长时间到指定time_point时刻如果未获取锁就放弃
auto begin = chrono::steady_clock::now();
auto tti = time(NULL);
tm tm1;
#ifdef _WIN32
localtime_s(&tm1, &tti);
#else
tm1 = *localtime(&tti);
#endif


tm1.tm_sec += 5;
auto tti2 = mktime(&tm1);
auto ti = chrono::system_clock::from_time_t(tti2); // 换算需要耗费时间
if (m2.try_lock_until(ti)) {
auto offset = chrono::steady_clock::now() - begin;
auto millseconds1 = chrono::duration_cast<chrono::milliseconds>(offset);
TLOG("try_lock_util millseconds", millseconds1.count());
m2.unlock();


}




}


/////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *argv[])
{
std::cout << "---------- 1、语法说明 ---------------" << std::endl;
// C++11 的 mutex 类别有 std::mutex(普通互斥量),std::timed_mutex(带超时的互斥量), std::recursive_mutex(重入互斥量), recursive_timed_mutex(重入时间互斥量)。
// mutex 只有默认构造函数,不支持复制构造函数。
std::cout << "---------- 2、使用说明 ---------------" << std::endl;
// 1、mutex 使用方法 lock() 和 unlock() 必须成对出现,不然会出现锁不释放造成线程挂起,或者 unlock 多调用导致共享变量提前解锁崩溃的情况。


// 2、对于可重入 recursive_mutex,在一个线程获取锁后,在调用 unlock() 之前会一直持有锁,这个线程会一直保留锁,
// 如果这个线程调用其他函数也尝试同一个 recursive_mutex,那么也会成功,即同一个线程可同时获取多次同一个可重入锁。
// recursive_mutex会计数获取锁的次数,lock() + 1,unlock() - 1. 必须成对出现,不然会出现第一种情况。


// 3、对于超时 timed_mutex 的 try_lock_xx, 就是在指定秒数或时刻这段时间或尝试获取锁,如果超过这个时间就会放弃获取锁,执行后边的代码。


std::cout << "---------- 3、使用场景 ---------------" << std::endl;
MutexLock(10, 100);
MutexTryLock(10, 100);
RecursiveMutex(10);
TimeMutex();


system("pause");


return 0;
}

C++11标准库:lock 锁

lock : 锁是一个对象,它可以持有对互斥锁的引用,并且可以在锁销毁期间(例如在离开块作用域时)解锁互斥锁。线程可以使用锁来帮助以异常安全的方式管理互斥锁的所有权。

lock.cpp

#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
#include <functional>


using namespace std;


/*
1、语法说明
2、使用说明
3、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << ":" << s << std::endl;
#define TLOG(t,s) std::cout << t << ":" << s << std::endl;


static std::mutex m;
static std::mutex m1;
void Lock(int count, int loopTimes)
{
auto args = new std::vector<int>();
shared_ptr<std::vector<int>> sp_args(args);
auto threads = new std::vector<thread*>();
shared_ptr<std::vector<thread*>> sp_threads(threads);


for (int i = 0; i < count; ++i) {
auto t1 = new thread([&args](mutex *m, mutex *m1, int loopTimes) {


for (int j = 0; j < loopTimes; ++j) {
// 默认获取锁
std::unique_lock<std::mutex> lk(*m);
args->push_back(j);


// 支持move操作
std::unique_lock<std::mutex> lk2(std::move(lk));
args->push_back(j);
}


for (int j = 0; j < loopTimes; ++j) {
// 先绑定,先不获取锁
std::unique_lock<std::mutex> lk(*m, std::defer_lock);
// 不需要匹配unlock(),因为lk在析构时会释放锁.
if (lk.try_lock()) {
args->push_back(j);
}
}


for (int j = 0; j < loopTimes; ++j) {
// 同步多个锁,这样避免在释放时没有匹配所有unlock.
std::unique_lock<std::mutex> lk(*m, std::defer_lock);
std::unique_lock<std::mutex> lk2(*m1, std::defer_lock);
// 不需要匹配unlock(),因为lk在析构时会释放锁.
if (std::try_lock(lk, lk2)) {
args->push_back(j);
}
}


for (int j = 0; j < loopTimes; ++j) {
// 同步多个锁,这样避免在释放时没有匹配所有unlock.
std::unique_lock<std::mutex> lk(*m, std::defer_lock);
std::unique_lock<std::mutex> lk2(*m1, std::defer_lock);
// 不需要匹配unlock(),因为lk在析构时会释放锁.
std::lock(lk, lk2);
args->push_back(j);
}


hash<thread::id> hasher;
auto _id = hasher(this_thread::get_id());
TLOG("thread finish hash id: ", _id);
}, &m, &m1, loopTimes);
threads->push_back(t1);
}


for (auto one : *threads) {
one->join();
delete one;
}


auto size = args->size();
LOG(size);
LOG(count*loopTimes);
LOG((count* loopTimes == size));
}




/////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *argv[])
{
std::cout << "---------- 1、语法说明 ---------------" << std::endl;
// C++11的<mutex>里增加了一个unique_lock类,用来管理一个或多个互斥量。
// 它不需要匹配调用 lock() 和 unlock(),因为它的实例的析构会释放锁,支持合并多个锁和移动锁对象;但是不支持可重入锁。
std::cout << "---------- 2、使用说明 ---------------" << std::endl;
// 当有两个共享变量需要不同的 mutex 加锁时?我们使用 lock(),unlock() 如果不匹配的话很容易造成死锁。
// unique_lock 提供合并锁:同时需要锁定多个 mutex 才会执行下一步,unique_lock 析构时也会同时释放。
std::cout << "---------- 3、使用场景 ---------------" << std::endl;


Lock(10, 100);
system("pause");


return 0;
}

C++11标准库:condition-variable 条件变量

condition-variable :C++11 增加了 库,库里有类 condition_variable 类是同步原语, 能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。

condition-variable.cpp

#include <algorithm>
#include <queue>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <random>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <functional>


using namespace std;


/*
1、语法说明
2、使用说明
3、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << ":" << s << std::endl;
#define TLOG(t,s) std::cout << t << ":" << s << std::endl;


static mutex m;
static condition_variable cv;
bool ready = true;




int rand_int(int low, int max)
{
static default_random_engine re;
using Dist = uniform_int_distribution<int>;
static Dist dt;
return dt(re, Dist::param_type(low, max));
}


void FuncFirst(std::queue<int>* args, mutex* m, condition_variable* cv)
{
PRINT("FuncFirst BEGIN");
int count = 0;
do {
std::unique_lock<std::mutex> lk(*m);
// 接收到通知,但是还是要判断是否Second是否准备好接收数据
cv->wait(lk, []() {return ready; });
auto value = rand_int(100, 10000);
args->push(value);
TLOG("push", value);
ready = false;
lk.unlock();
cv->notify_all();
if (++count == 10)
break;
} while (true);
PRINT("FuncFirst END");
}


void FuncSecond(std::queue<int>* args, mutex* m, condition_variable* cv)
{
PRINT("FuncSecond BEGIN");
int count = 0;
do {
std::unique_lock<std::mutex> lk(*m);
cv->wait(lk, []() {return !ready; });
while (!args->empty()) {
TLOG("pop", args->front());
args->pop();
}


ready = true;
lk.unlock();
cv->notify_all();
if (++count == 10)
break;


} while (true);
PRINT("FuncSecond END");
}


void ConditionVariable()
{
std::queue<int> args;
thread t1(FuncFirst, &args, &m, &cv);
thread t2(FuncSecond, &args, &m, &cv);
t1.join();
t2.join();


}


/////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *argv[])
{
std::cout << "---------- 1、语法说明 ---------------" << std::endl;
// 类 condition_variable 创建时使用默认构造函数,不允许使用复制构造函数。
std::cout << "---------- 2、使用说明 ---------------" << std::endl;
// 1、condition_variable(条件变量)一般用在控制多线程的顺序上,某个线程没任务时需要等待条件变量通知才能继续执行;
// 比如线程A需要等待线程B通知某个条件满足了,之后A收到通知后,执行逻辑。
// 而B线程可以等待或不等待A执行结束,B可以继续执行自己的逻辑。


// 2、条件变量需要满足条件下才会执行逻辑,而锁是只要得到锁即可继续执行,而没满足条件时进入等待,比单纯加锁 CPU 利用率高。


// 3、条件变量 wait 结束后,也是会重新获取锁;继续执行的语句也是在获取锁的情况下执行;也就是说条件变量和 mutex 是搭配使用的。
std::cout << "---------- 3、使用场景 ---------------" << std::endl;
// 条件变量,最常用的就是用 wait() 和 notify_xx() 方法,一个是等待通知,一个通知等待可以继续执行。
// 如果有多个等待,那么锁的获取即使竞赛获取的,哪个线程先获取不确定。以下实现简单生产消费者模式的两个线程。


ConditionVariable();
system("pause");


return 0;
}

C++11标准库:atomic 原子对象

atomic :C++11 的 STL库在并发支持上除了有 互斥量之外,还有 原子库,不同于互斥量的锁,,原子库的类和函数是无锁的。

atomic.cpp

#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <string.h>
#include <memory>
#include <thread>
#include <mutex>
#include <chrono>
#include <functional>
#include <atomic>


using namespace std;


/*
1、语法说明
2、使用说明
3、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << "->" << s << std::endl;
#define TLOG(t,s) std::cout << t << "->" << s << std::endl;


static mutex m;


void Atomic(int count)
{
PRINT("============ Atomic ==========");
// typedef atomic<int> atomic_int;
// 创建并初始化值
atomic_int at(0);
// +=operator 相当于 at.fetch_add(arg), 原子操作.
at += 5;
TLOG("+=", at);


// 原子加法
at.fetch_add(5);
TLOG("fetch_add", at);


// 比较原值是否是期望的值,如果是,那么修改原值
// atomic_compare_exchange_strong: 强类型不需要while循环比较,没有虚假地址失败.
// 大多数情况下用强类型比较即可.
int at1 = 10;
if (std::atomic_compare_exchange_strong(&at, &at1, 90))
TLOG("atomic_compare_exchange_strong", at);


// atomic_compare_exchange_weak
// 函数的弱形式( (1) 与 (3) )允许虚假地失败,即表现为如同 *obj != *expected ,即使它们相等。
// 当比较并交换在循环中时,弱版本在某些平台上会生成更好的性能。
at1 = at;
while (!atomic_compare_exchange_weak(&at, &at1, 100));
TLOG("atomic_compare_exchange_strong", at);




// 使用内存顺序,读取写入指定内存顺序,一般用在内存块比较多的情况下进行优化。数值整型性能影响不大.
int i2 = 100;
// memory_order_relaxed 宽松操作:没有同步或顺序制约,仅对此操作要求原子性
// memory_order_release 有此内存顺序的存储操作进行释放操作:当前线程中的读或写不能被重排到此存储后。
// 当前线程的所有写入,可见于获得该同一原子变量的其他线程释放获得顺序),
// 并且对该原子变量的带依赖写入变得对于其他消费同一原子对象的线程可见
if (std::atomic_compare_exchange_strong_explicit(&at, &i2, -90,
std::memory_order_release, std::memory_order_relaxed)) {
TLOG("atomic_compare_exchange_strong_explicit", at);
}


// 多线程+,无锁,效率比 mutex.lock() 高很多.
// 值没有因多线程出现互相覆盖的情况.
atomic_int at2(0);
std::vector<thread> args;
for (int i = 0; i < count; ++i) {
args.emplace_back([](atomic_int* a) {
for (int j = 0; j < 100; ++j) {
// a->fetch_add(1);
(*a) += 1;
}
}, &at2);
}


for (auto &one : args) {
one.join();
}


LOG(at2);
LOG(count * 100);
LOG((at2 == count * 100));
}




void AtomicClass(int count)
{
PRINT("============ AtomicClass ==========");
// 原子操作字符串指针,一般用在显示进度文字。
atomic<char*> a(nullptr);
std::vector<thread> args;
for (int i = 0; i < count; ++i) {
args.emplace_back([](atomic<char*>* a, int index) {
auto str = "hello:" + std::to_string((long long)index);
auto size = str.size();
auto buf = (char*)malloc(size + 1);
#ifdef _WIN32
strncpy_s(buf, size + 1, str.c_str(), size);
#else
strncpy(buf, str.c_str(), size);
#endif // _WIN32




buf[size] = 0;
char*atomicBuf = NULL;
m.lock();
TLOG("buf: ", buf);
m.unlock();
if ((atomicBuf = atomic_exchange(a, buf))) {
m.lock();
TLOG("atomicBuf: ", atomicBuf);
m.unlock();
free(atomicBuf);
}


}, &a, i);
}


for (auto &one : args) {
one.join();
}


char* atomicBuf = NULL;
if ((atomicBuf = atomic_exchange(&a, (char*)NULL))) {
TLOG("Last atomicBuf: ", atomicBuf);
free(atomicBuf);
}


// 不允许使用string作为特化类型,因为string不是 trivially copyable(扁平可复制的) 类型。
// 多线程情况下, 行为是未定义的。可以是C结构体或者我们之前讲的章节"Generalized PODs,广义的扁平简单数据类型)。
// 编译失败
// error: static assertion failed: std::atomic requires a trivially copyable type
// atomic<string> at("");
// atomic_exchange(&at,std::string("vic.MINg"));


// https://zh.cppreference.com/w/cpp/atomic/memory_order




}


/////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *argv[])
{
std::cout << "---------- 1、语法说明 ---------------" << std::endl;
// 类std::atomic创建时可以使用默认构造函数,也有带特化类型的初始化参数, 不支持复制构造函数。
std::cout << "---------- 2、使用说明 ---------------" << std::endl;
// 1、atomic<T>原子类只支持扁平的数据结构类型,扁平数据结构即可以通过memcpy进行位复制的类型,而不影响该类的使用。
// 比如C类型 struct, 原始数值类型,union类型,PODs类型等。


// 2、原子类型操作通过原子对象的方法进行数值运算。
// 或者通过全局函数 std::atomic_exchange 等进行数值运算, 可对应上 Windows 的 Win32函数 InterlockedExchange 等。


// 3、在多线程场景下,能用原子操作的就不要使用 <mutex> 互斥量,因为原子操作性能高很多倍。
// 原子函数或对象的操作是保证修改或读取的对象不会因为多核情况下并发修改导致错误的值得情况。
// 但是一些复合类型,比如 std::vector 并不是 PODs 对象,是用不了原子操作的。
std::cout << "---------- 3、使用场景 ---------------" << std::endl;
Atomic(10);
AtomicClass(10);


system("pause");
return 0;
}


C++11标准库:promise-future 承诺和未来

promise-future :C++11 提供了从一个单独线程中产生的任务返回一个值的(promise)承诺和(future)未来,并提供了 packaged_task 来帮助启动任务。future 可以理解为获取异步值,promise 可以理解为设置异步共享值。

promise-future.cpp

#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <assert.h>
#include <chrono>
#include <functional>
#include <future>


using namespace std;




/*
1、语法说明
2、使用说明
3、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << "->" << s << std::endl;
#define TLOG(t,s) std::cout << t << "->" << s << std::endl;


static std::future<string> gStr;


string GetDirName(const char* _id) {
LOG(_id);
return "Music:" + std::string(_id);
}


void FuturePromise()
{
PRINT("=============== FuturePromise =============");


// 调用 async 返回一个 std::future 对象.
// 表现如同以 policy 为 std::launch::async | std::launch::deferred 调用 (2) 。
// 换言之, f 可能执行于另一线程,或者它可能在查询产生的 std::future 的值时同步运行。
gStr = std::move(std::async(GetDirName, "1.1 default policy"));
this_thread::sleep_for(chrono::seconds(2));
PRINT("Call GetDirName");
auto str = gStr.get();
TLOG("1.1 The Future", str);


gStr = std::move(std::async(GetDirName, "1.2 default policy"));
this_thread::sleep_for(chrono::seconds(2));
PRINT("Call GetDirName");
str = gStr.get();
TLOG("1.2 The Future", str);


// 调用惰性策略,调用future.get()或future.wait()求值
auto _future2 = std::async(std::launch::deferred, GetDirName, "2. deferred policy");
this_thread::sleep_for(chrono::seconds(2));
PRINT("Call GetDirName");
str = _future2.get();
TLOG("2. The Future", str);


// 调用thread来获取值
promise<int> _promise;
auto _future3 = _promise.get_future();
thread _thread([](promise<int> *pPromise) {
PRINT("thread promise set value BEGIN");
pPromise->set_value(90);
this_thread::sleep_for(chrono::seconds(2));
PRINT("thread promise set value END");
}, &_promise);


// 阻塞直到结果可用,类似条件变量的通知,注意,线程不一定结束.
// 不允许调用多次,调用一次返回值后,共享状态时未定义的。调用多次会抛出异常
_future3.wait();
// wait()之后,get()之前判断为true
assert(_future3.valid());
// f3.get()调用后释放任何共享状态,即再调用f3.get()是未定义行为.
LOG(_future3.get());
// 调用get()之后为false.
assert(!_future3.valid());
// 崩溃
// LOG(_future3.get());
_thread.join();


// 调用packaged_task绑定函数,并不创建线程。
packaged_task<std::string()> _task(bind(GetDirName, "packaged_task"));
std::future<string> result = _task.get_future();
std::thread _task_thread(std::move(_task));
_task_thread.join();
LOG(result.get());
}




void ShareFuture()
{
PRINT("=============== ShareFuture =============");
// 允许多线程通过 shared_future 来 wait() 等待消息,直到 promise.set_value(),
// 类似条件变量的 notify_all 效果.
// https://zh.cppreference.com/w/cpp/thread/shared_future
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(); // waits for the signal from 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(); // waits for the signal from 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);


// wait for the threads to become ready
t1_ready_promise.get_future().wait();
t2_ready_promise.get_future().wait();


// the threads are ready, start the clock
start = std::chrono::high_resolution_clock::now();


// signal the threads to go
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";
}
/////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *argv[])
{
std::cout << "---------- 1、语法说明 ---------------" << std::endl;
// 类 future 的构造函数只支持默认构造函数和移动构造函数,而 shared_future(共享future)更是支持复制构造函数。而 promise 和 future 一样,不支持复制构造函数。
std::cout << "---------- 2、使用说明 ---------------" << std::endl;
// 1、并发编程可能很困难,特别是如果您试图巧妙地使用线程和锁。如果必须使用条件变量或原子(用于无锁编程),则会更加困难。
// C++11提供了从一个单独线程中产生的任务返回一个值的未来和承诺,并提供了 packaged_task 来帮助启动任务。
// 关于 future 和 promise 的重要一点是,它们支持在两个任务之间传输一个值,而不需要显式地使用锁;“系统”有效地实现了转移。
// 基本思想很简单;当一个任务想要向启动它的线程返回一个值时,它将该值放入一个承诺。
// 在某种程度上,实现使该值出现在未来的承诺中。然后调用者(通常是任务的启动器)可以读取值。有关附加的简单性,请参见 async()。


// 2、类模板 std::shared_future 提供了一种机制来访问异步操作的结果,类似于std::future,只是允许多个线程等待相同的共享状态。
// 与 std::future 不同,它只有 moveable(所以只有一个实例可以引用任何特定的异步结果),std::shared_future是可复制的,多个shared future对象可以引用相同的共享状态。
// 如果每个线程都通过自己的 shared_future 对象的副本来访问相同的共享状态,则从多个线程访问该状态是安全的。


std::cout << "---------- 3、使用场景 ---------------" << std::endl;
FuturePromise();
ShareFuture();


system("pause");
return 0;
}

C++11标准库:async 异步函数

async :C++11 提供了一个 async() 函数执行异步逻辑, 相当于创建了一个thread线程,不同于 thread 的是,async 可以先创建,需要时再执行。

async.cpp

#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <chrono>
#include <future>
#include <functional>


using namespace std;


/*
1、语法说明
2、使用说明
3、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << ":" << s << std::endl;
#define TLOG(t,s) std::cout << t << ":" << s << std::endl;


static std::future<string> gStr;


string GetDirName(const char* _id) {
LOG(_id);
return "Async:" + std::string(_id);
}


/////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *argv[])
{
std::cout << "---------- 1、语法说明 ---------------" << std::endl;
// 模板函数 async 异步运行函数(可能在一个单独的线程中,它可能是线程池的一部分),并返回一个 std::future,它将最终保存该函数调用的结果。
std::cout << "---------- 2、使用说明 ---------------" << std::endl;
// async()有两种执行策略:一种是异步策略std::launch::async,一种是惰性策略std::launch::deferred。
// std::launch::async 策略在调用函数时即执行,而 std::launch::deferred 策略只有在 future 调用 get() 或者 wait() 的时候才会执行(即执行绑定async绑定的函数)。
std::cout << "---------- 3、使用场景 ---------------" << std::endl;
// 调用 async 返回一个 std::future 对象.
// 表现如同以 policy 为 std::launch::async | std::launch::deferred 调用 (2) 。
// 换言之, f 可能执行于另一线程,或者它可能在查询产生的 std::future 的值时同步运行。
gStr = std::move(std::async(GetDirName, "1. default policy"));
this_thread::sleep_for(chrono::seconds(2));
PRINT("Call GetDirName");
auto str = gStr.get();
TLOG("1.The Future", str);


// 调用惰性策略,调用future.get()或future.wait()求值
gStr = std::move(std::async(std::launch::deferred, GetDirName, "2. deferred policy"));
this_thread::sleep_for(chrono::seconds(2));
PRINT("Call GetDirName");
str = gStr.get();
TLOG("2.The Future", str);


system("pause");
return 0;
}

C++11标准库:thread-local 线程本地存储

thread-local :C++ 11新增了一个thread_local修饰符,直接修饰变量来声明一个线程本地存储变量,方便易用。

#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <chrono>
#include <string.h>
#include <functional>
#include <mutex>


using namespace std;


/*
1、语法说明
2、使用说明
3、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << ":" << s << std::endl;
#define TLOG(t,s) std::cout << t << ":" << s << std::endl;




thread_local char* buf = NULL;
void PrintThreadLocal()
{
LOG(buf);
}


void ThreadLocal(int count)
{


std::vector<thread> args;
for (int i = 0; i < count; ++i) {
args.emplace_back([](int index) {
buf = (char *)malloc(512);
auto str = std::to_string(index);
#ifdef _WIN32
strncpy_s(buf, 512, str.c_str(), str.size());
#else
strncpy(buf, str.c_str(), str.size());
#endif


buf[str.size()] = 0;
PrintThreadLocal();


free(buf);
buf = NULL;
}, i);
}


for (auto& one : args) {
one.join();
}
}


/////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *argv[])
{
std::cout << "---------- 1、语法说明 ---------------" << std::endl;
// thread_local - 线程存储期
// 线程(thread)存储期。对象的存储在线程开始时分配,而在线程结束时解分配。每个线程拥有其自身的对象实例。
// 唯有声明为 thread_local 的对象拥有此存储期。thread_local 能与 static 或 extern 一同出现,以调整连接。
// 关于具有此存储期的对象的初始化的细节,见非局部变量和静态局部变量。


// 语法:
// thread_local char* buf;
// static thread_local char* buf;
// extern thread_local char* buf;


std::cout << "---------- 2、使用说明 ---------------" << std::endl;
// 1、thread_local 修饰符的变量,它的生命周期是线程运行的范围内。
// 在一个线程执行的函数直接引用 thread_local 变量,它的值是在线程执行范围内的,并不局限于某个方法,或者全局。
// thread_local 的变量在创建堆内存时,在线程结束前需要释放,避免内存泄漏。


// 2、对于 Win32 线程 或 pthread 支持的线程。
std::cout << "---------- 3、使用场景 ---------------" << std::endl;
ThreadLocal(10);


system("pause");
return 0;
}

C++11标准库:abandom-process 进程中止

abandom-process :C++11 新增两个程序中止处理函数来分别处理快速退出或异常退出的情况。

abandom-process.cpp

#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <assert.h>
#include <stdlib.h>
#include <chrono>
#include <functional>
#include <future>
#include <exception>




using namespace std;




/*
1、使用场景
*/


#define PRINT(s) std::cout << s << std::endl;
#define LOG(s) std::cout << #s << "->" << s << std::endl;
#define TLOG(t,s) std::cout << t << "->" << s << std::endl;


void OnDestroy1()
{
PRINT("OnDestroy1");
}


void OnDestroy2()
{
PRINT("OnDestroy2");
}


void AbandomProcess1()
{
// 快速中止:
// void at_quick_exit(void(*)()); // 退出处理函数,在进程退出之前调用处理函数. 可添加最少32个
// void quick_exit(int exitCode); // 快速退出


// 允许至少32个处理函数.
at_quick_exit(OnDestroy1);
at_quick_exit(OnDestroy2);
thread t1([]() {
PRINT("thread error! ready to exit");
std::quick_exit(-1);
});
t1.join();
}


void AbandomProcess2()
{
// 异常快速中止:
// std::terminate_handler set_terminate(std::terminate_handler f); // 全局唯一处理函数,在抛出
// 异常未处理或调用 std::terminate() 时调用。
// 只允许一个处理函数


std::set_terminate(OnDestroy1);
thread t1([]() {
PRINT("thread throw exception or call std::terminate()");
// throw 1;
std::terminate();
});
t1.join();
}


/////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char const *argv[])
{
std::cout << "---------- 1、使用场景 ---------------" << std::endl;


AbandomProcess1();




// AbandomProcess2();


system("pause");
return 0;
}


文章转载自林元皓,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论