《C++新经典》第17章 并发与多线程

  • 17.1 基本概念和实现
    • 17.1.1 并发、进程、线程的基本概念和综述
    • 17.1.2 并发的实现方法
  • 17.2 线程启动、结束与创建线程写法
    • 17.2.1 线程开始与结束
    • 17.2.2 其它线程创建方法
  • 17.3 线程传参、detach与成员函数作为线程函数
    • 17.3.1 传递临时对象作为线程参数
    • 17.3.2 临时对象作为线程参数续
    • 17.3.3 传递类对象与智能指针作为线程参数
    • 17.3.4 成员函数作为线程入口函数
  • 17.4 多个线程、数据共享与案例代码
    • 17.4.1 创建和等待多个线程
    • 17.4.2 数据共享
    • 17.4.3 案例代码
  • 17.5 互斥量概念、用法、死锁与解决
    • 17.5.1 互斥量概念
    • 17.5.2 互斥量用法
    • 17.5.3 死锁
  • 17.6 unique_lock详解
    • 17.6.1 unique_lock取代lock_guard
    • 17.6.2 unique_lock的第二个参数
    • 17.6.3 unique_lock的成员函数
    • 17.6.4 unique_lock所有权的传递
  • 17.7 单例设计模式共享数据分析、解决与call_once
    • 17.7.1 设计模式简单谈
    • 17.7.2 单例设计模式
    • 17.7.3 单例设计模式共享问题分析、解决
    • 17.7.4 std::call_once
  • 17.8 condition_variable、wait、notify_one与notify_all
    • 17.8.1 条件变量condition_variable、wait与notify_one
    • 17.8.2 wait与notify思考
    • 17.8.3 notify_all
  • 17.9 async、future、packaged_task和promise
    • 17.9.1 std::async和std::future创建后台任务并返回值
    • 17.9.2 std::packaged_task
    • 17.9.3 std::promise
  • 17.10 future其它成员函数、shared_future与atomic
    • 17.10.1 std::future其它成员函数
    • 17.10.2 续谈std::async不确定性问题
    • 17.10.3 std::shared_future
    • 17.10.4 原子操作std::atomic
  • 17.11 Windows临界区与其它各种mutex互斥量
    • 17.11.1 Windows临界区
    • 17.11.2 多次进入临界区试验
    • 17.11.3 自动析构技术
    • 17.11.4 recursive_mutex递归的独占互斥量
    • 17.11.5 带超时的互斥量std::timed_mutx和std::recursive_timed_mutex
  • 17.12 补充知识、线程池浅谈、数量谈与总结
    • 17.12.1 知识点补充
    • 17.12.2 线程池浅谈
    • 17.12.3 线程创建数量谈

17.1 基本概念和实现

17.1.1 并发、进程、线程的基本概念和综述

  1. 并发
    多个任务(独立活动)同时发生(进行)。
    单CPU,操作系统调度,任务切换。
    多CPU,硬件并发(并行)。

  2. 可执行程序
    Windows下exe文件,Linux下有可执行权限的文件(-rwxrw-r–)。

  3. 进程
    一个可执行程序运行起来创建一个进程,进程就是运行起来了的可执行程序。

  4. 线程
    每个进程有唯一主线程,随进程启动。
    线程理解为一条代码的执行通路(道路)。
    每个线程需要独立的堆栈空间(耗费内存,1MB左右),线程切换需保存很多中间状态。上下文切换必须但无价值和意义的额外工作,耗费资源。

17.1.2 并发的实现方法

  1. 多进程并发
    多个可执行程序运行。同一计算机上进程通过管道、文件、消息队列、共享内存等技术通信,不同计算机间进程通过socket(网络套接字)等通信。进程直接数据保护问题,相互通信复杂(即使同一台计算机上)。

  2. 多线程并发
    单个进程创建多个线程(轻量级进程,独立运行),共享进程地址空间(共享内存)、全局变量、指针、引用等。

  3. 总结
    与多进程并发相比较,多线程并发的优缺点:
    优点:线程轻量级,启动速度更快;系统资源开销更少;执行速度更快。
    缺点:使用有难度,小心数据一致性问题。

17.2 线程启动、结束与创建线程写法

17.2.1 线程开始与结束

主线程执行完后,未执行完的子线程会被操作系统强制终止(detach例外)。

#include <iostraem>
#include <thread>
using namespace std;void myprint() {cout <<"线程开始" <<endl;cout <<"线程结束" <<endl;
}int main() {thread mytobj(myprint);//函数myprint是可调用对象mytobj.join();cout <<"main主函数结束" <<endl;return 0;
}

线程detach后,线程关联的thread对象失去与线程的关联,线程驻留在后台运行(控制台不会输出),由C++运行时库接管(负责清理该线程相关的资源),类似守护线程。

#include <iostraem>
#include <thread>
using namespace std;void myprint() {cout <<"线程开始" <<endl;cout <<"线程结束1" <<endl;cout <<"线程结束2" <<endl;cout <<"线程结束3" <<endl;cout <<"线程结束4" <<endl;cout <<"线程结束5" <<endl;cout <<"线程结束6" <<endl;cout <<"线程结束7" <<endl;cout <<"线程结束8" <<endl;
}int main() {thread mytobj(myprint);//函数myprint是可调用对象mytobj.detach(); //主线程结束后,myprint子线程转入后台执行,看不见输出结果(输出结果的窗口关联的是主线程)cout <<"main主函数结束" <<endl;return 0;
}
thread mytobj(myprint);
if(mytobj.joinable())cout <<"joinable() == true" <<endl;
mytobj.join();//或者mytobj.detach();
//joinable()用于判断线程是否调用过join或者detach
if(!mytobj.joinable())cout <<"joinable() == false" <<endl;

17.2.2 其它线程创建方法

  1. 类创建线程
class TA {public:void operator()() {//重载(),可调用对象cout <<"TA::operator()开始" <<endl;cout <<"m_i: " <<m_i <<endl;cout <<"TA::operator()结束" <<endl;}TA(int i):m_i(i){cout <<"TA(int i), this=" <<this <<endl;}~TA() {cout <<"~TA(), this=" <<this <<endl;}TA(const TA& ta):m_i(ta.m_i){cout <<"TA(const TA& ta), this=" <<this <<endl;}int m_i;//隐患//引用值,当main线程结束时,变量myi会销毁,引用无效//TA(int& i):m_i(i){}//int& m_i;
};int myi = 6;
TA ta(myi);//ta对象会被复制到子线程,main线程销毁ta对子线程无影响。
thread mytobj(ta); //thread mytobj(TA(myi));临时对象,编译出错//ta.join();
ta.detach();cout <<"main结束" <<endl;
  1. lambda表达式创建线程
auto mylamthread = [] {cout <<"线程开始" <<endl;cout <<"线程结束" <<endl;
};
thread mytobj(mylamthread);
mytobj.join();//或者mytobj.detach();

17.3 线程传参、detach与成员函数作为线程函数

17.3.1 传递临时对象作为线程参数

  1. 陷阱1
    detach创建线程时,不要往线程中传递引用、指针类参数。
//const引用会产生临时对象
void myprint(int i, const string& mybuf) {}
  1. 陷阱2
void myprint(int i, const string& mybuf) {}
int mvar = 1;
char mybuf[] = "test";
//mybuf转换为string时机不确定,潜在问题
thread mytobj(myprint, mvar, mybuf);//构造string临时对象,无问题
thread mytobj(myprint, mvar, string(mybuf));

自定义类测试

#include <iostream>
#include <thread>
using namespace std;class A
{public:A(int a) : m_i(a) { cout << "A::A(int a)" << this << endl; }A(const A &a) { cout << "A::A(const A& a)" << this << endl; }~A() { cout << "~A::A()" << this << endl; }private:int m_i;
};void myprint(int i, const A &mybuf)
{cout << &mybuf << endl;
}int main()
{int mvar = 1;int secondvar = 12;// 拷贝构造函数可能未执行,main线程就结束了//thread mytobj(myprint, mvar, secondvar);// main线程,执行一次构造函数+一次拷贝构造函数(生成类A实例位于子线程中)+一次析构函数// 子线程中,输出实例A地址+一次析构函数thread mytobj(myprint, mvar, A(secondvar));//mytobj.join();mytobj.detach();cout << "main over" << endl;return 0;
}
  1. 总结
    int这种简单类型,使用值传递,不要使用引用;
    类对象作为参数时,避免隐式类型转换,直接在创建线程时构建临时对象,线程函数形参使用引用;
    建议使用join,不使用detach,这样无局部变量失效导致线程非法内存引用的问题。

17.3.2 临时对象作为线程参数续

  1. 线程id
    标识线程的唯一数字。
std::this_thread::get_id()
  1. 临时对象构造时机
#include <iostream>
#include <thread>
using namespace std;class A
{public:A(int a) : m_i(a) { cout << "A::A(int a) this: " << this <<" id: "<< this_thread::get_id() << endl; }A(const A &a) { cout << "A::A(const A& a) this: " << this <<" id: "<< this_thread::get_id() << endl; }~A() { cout << "~A::A() this: " << this <<" id: "<< this_thread::get_id() << endl; }private:int m_i;
};void myprint2(const A &mybuf)
{cout <<"this: "<< &mybuf <<" id: "<< this_thread::get_id() << endl;
}//void myprint2(const A mybuf)
//非引用时,
//子线程中会增加一次拷贝构造函数+一次析构函数int main()
{cout << "main id: " << this_thread::get_id() << endl;int mvar = 1;//子线程中利用变量mvar创建A实例//mvar可能因main执行完毕而回收,潜在问题。//thread mytobj(myprint2, mvar);// main线程执行构造函数和拷贝构造函数(创建两个A的实例)// 构造函数创建的实例A执行完拷贝构造后,main线程析构// 拷贝构造函数创建的实例A归子线程所有,且由子线程析构thread mytobj(myprint2, A(mvar));mytobj.join();cout << "main over" << endl;return 0;
}

建议

//函数使用类的常量引用
void myprint2(const A& mybuf);//创建线程时构建临时对象
thread mytobj(myprint2, A(mvar));

17.3.3 传递类对象与智能指针作为线程参数

临时对象不能作为非const引用参数。

#include <iostream>
#include <thread>
using namespace std;class A
{public:A(int a) : m_i(a) { cout << "A::A(int a) this: " << this << " id: " << this_thread::get_id() << endl; }A(const A &a) { cout << "A::A(const A& a) this: " << this << " id: " << this_thread::get_id() << endl; }~A() { cout << "~A::A() this: " << this << " id: " << this_thread::get_id() << endl; }mutable int m_i; // mutable,const&可修改
};//c++只会为const引用产生临时对象
//临时对象不能作为非const引用
void myprint2(const A &a)
{a.m_i = 199;
}int main()
{A myobj(10);//thread t(myprint2, myobj); //子线程调用拷贝构造函数生成A实例thread t(myprint2, std::ref(myobj)); //真引用main线程的A实例t.join();cout << myobj.m_i << endl;cout << "main over" << endl;return 0;
}
#include <iostream>
#include <thread>
using namespace std;class A
{public:A(int a) : m_i(a) { cout << "A::A(int a) this: " << this << " id: " << this_thread::get_id() << endl; }A(A &a) { cout << "A::A(A& a) this: " << this << " id: " << this_thread::get_id() << endl; }~A() { cout << "~A::A() this: " << this << " id: " << this_thread::get_id() << endl; }int m_i;
};void myprint2(A &a)
{a.m_i = 199;cout << "myprint2(A &a) this: " << &a << " id: " << this_thread::get_id() << endl;
}int main()
{A myobj(10);thread t(myprint2, std::ref(myobj)); //真传递引用,可修改t.join();cout << myobj.m_i << endl;cout << "main over" << endl;return 0;
}
#include <iostream>
#include <thread>
using namespace std;void myprint3(unique_ptr<int> pzn)
{*pzn = 22;
}int main()
{unique_ptr<int> myp(new int(100));cout<< *myp <<endl;thread t(myprint3, std::move(myp));//unique_ptr转移到子线程,myp为空cout << "myp==nullptr: "<<(myp==nullptr) <<endl;t.join(); //必须等待,main线程创建内存空间myp,子线程在使用。cout << "main over" << endl;return 0;
}

17.3.4 成员函数作为线程入口函数

#include <iostream>
#include <thread>
using namespace std;class A
{public:A(int a) : m_i(a) { cout << "A::A(int) this: " << this << " id: " << this_thread::get_id() << endl; }A(A &a) { cout << "A::A(A&) this: " << this << " id: " << this_thread::get_id() << endl; }~A() { cout << "~A::A() this: " << this << " id: " << this_thread::get_id() << endl; }public:void thread_work(int num){cout << "this: " << this << " id: " << this_thread::get_id() << endl;}int m_i;
};class A2
{public:A2(int a) : m_i(a) { cout << "A2::A2(int) this: " << this << " id: " << this_thread::get_id() << endl; }A2(A2 &a) { cout << "A2::A2(A2&) this: " << this << " id: " << this_thread::get_id() << endl; }~A2() { cout << "~A2::A2() this: " << this << " id: " << this_thread::get_id() << endl; }public:void operator()(int num){cout << "this: " << this << " id: " << this_thread::get_id() << endl;}int m_i;
};int main()
{if(0){A a(3);thread obj(&A::thread_work, a, 15);//拷贝构造函数主线程执行,析构函数子线程执行obj.join();}if(0){A a(3);thread obj(&A::thread_work, &a, 15);//thread obj(&A::thread_work, std::ref(a), 15);//同上,无拷贝构造函数,主线程和子线程共用类aobj.join();} if(0){A2 a(3);thread obj(a, 15);//拷贝构造函数主线程执行,析构函数子线程执行obj.join();}if(1){A2 a(3);//thread obj(&a, 15);//errorthread obj(std::ref(a), 15);//无拷贝构造函数,主线程和子线程共用类aobj.join();} cout << "over!\n";return 0;
}

17.4 多个线程、数据共享与案例代码

17.4.1 创建和等待多个线程

#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;void myprint(int num)
{cout << num << endl;
}#define N 5
int main()
{/*vector<thread> threads;for (int i = 0; i < N; i++)//threads.push_back(thread(myprint, i));threads.emplace_back(thread(myprint, i));*/vector<thread> threads(N);for (int i = 0; i < N; i++)threads[i] = thread(myprint, i);std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));/*for (int i = 0; i < N; i++)threads[i].join();*//*for (auto &entry : threads)entry.join();*//*for (auto iter = threads.begin(); iter != threads.end(); ++iter)iter->join();*/cout << "main over" << endl;return 0;
}

17.4.2 数据共享

只读没问题,读写需加锁。

17.4.3 案例代码

list频繁按序插入和删除数据时效率更高,vector随机插入和删除数据时效率更高。

#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <functional>
#include <list>
using namespace std;#define N 1000
class A
{public:void inMsgRecvQueue(){for (int i = 0; i < N; i++){msgRecvQueue.push_back(i);this_thread::sleep_for(chrono::seconds(3));}}void outMsgRecvQueue(){for (int i = 0; i < N; i++){if (msgRecvQueue.empty()){cout<<"empty"<<endl;}else{int command = msgRecvQueue.front();msgRecvQueue.pop_front();cout<<"do something"<<endl;}this_thread::sleep_for(chrono::seconds(1));}}private:list<int> msgRecvQueue;
};int main()
{A a;thread outObj(&A::outMsgRecvQueue, &a);thread inObj(&A::inMsgRecvQueue, std::ref(a));inObj.join();outObj.join();cout << "main over" << endl;return 0;
}

17.5 互斥量概念、用法、死锁与解决

17.5.1 互斥量概念

互斥量,mutex,一个类,一把锁。
保护需要保护的数据。保护数据少了,无保护效果;保护数据多了,影响程序运行效率。

17.5.2 互斥量用法

  1. lock和unlock
mutex mtx;
mtx.lock();mtx.unlock();
  1. lock_guard
mutex mtx;std::lock_guard<mutex> guard(mtx);

17.5.3 死锁

都等待对方释放锁,而都锁住了对方需要的锁,相互等待。

一般解决方法是按相同顺序上锁。

m1.lock();
m2.lock();
m2.unlock();
m1.unlock();
//或者
lock_guard<mutex> g1(m1);
lock_guard<mutex> g2(m2);

lock可以一次锁住两个及以上互斥量。(要么都锁住,要么都不锁)

std::lock(m1, m2);//m1,m2先后顺序无关m2.unlock();
m1.unlock();
mutex m1;
mutex m2;std::lock(m1, m2);//m1,m2先后顺序无关,同时锁住m1和m2lock_guard<mutex> g1(m1, std::adopt_lock);
lock_guard<mutex> g2(m2, std::adopt_lock);//lock_guard<mutex> g1(m1);
//g1构造函数中调用lock,析构函数调用unlock
//std::adopt_lock告诉g1构造函数中不调用lock

17.6 unique_lock详解

一般使用lock_guard。
unique_lock比lock_guard更灵活,执行效率差一点,内存占用多一点。

#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;#define N 5
class A
{public:void inMsgRecvQueue(){for (int i = 0; i < N; i++){lock_guard guard(mtx);msgRecvQueue.push_back(i);this_thread::sleep_for(chrono::milliseconds(30));}}void outMsgRecvQueue(){int command;for (int i = 0; i < N; i++){bool result = outMsgLULProc(command);if (!result){cout << "empty" << endl;}else{cout << "do something" << endl;}this_thread::sleep_for(chrono::milliseconds(10));}}private:bool outMsgLULProc(int &command){lock_guard guard(mtx);if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();return true;}return false;}private:list<int> msgRecvQueue;mutex mtx;
};int main()
{A a;thread outObj(&A::outMsgRecvQueue, &a);thread inObj(&A::inMsgRecvQueue, std::ref(a));inObj.join();outObj.join();cout << "main over" << endl;return 0;
}

17.6.1 unique_lock取代lock_guard

unique_lock可完全取代lock_guard。

17.6.2 unique_lock的第二个参数

  1. std::adopt_lock
    adopt_lock标记m已经lock过,g构造函数中不再调用lock。
mutex m;
m.lock();
unique_lock<mutex> g(m, adopt_lock);
  1. std::try_to_lock

#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;#define N 5
class A
{public:void inMsgRecvQueue(){for (int i = 0; i < N; i++){unique_lock guard(mtx, try_to_lock);//mtx不能lockif(guard.owns_lock()) //   拿到锁头msgRecvQueue.push_back(i);this_thread::sleep_for(chrono::milliseconds(30));}}void outMsgRecvQueue(){int command;for (int i = 0; i < N; i++){bool result = outMsgLULProc(command);if (!result){cout << "empty" << endl;}else{cout << "do something" << endl;}this_thread::sleep_for(chrono::milliseconds(10));}}private:bool outMsgLULProc(int &command){unique_lock guard(mtx);if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();return true;}return false;}private:list<int> msgRecvQueue;mutex mtx;
};int main()
{A a;thread outObj(&A::outMsgRecvQueue, &a);thread inObj(&A::inMsgRecvQueue, std::ref(a));inObj.join();outObj.join();cout << "main over" << endl;return 0;
}
  1. std::defer_lock
    初始化未加锁的mutex,mutex不能lock。
mutex mtx;
unique_lock guard(mtx, defer_lock);//mtx不能lock

17.6.3 unique_lock的成员函数

  1. lock
mutex mtx;
unique_lock guard(mtx, defer_lock);guard.lock();
  1. unlock
mutex mtx;
unique_lock guard(mtx);guard.unlock();
  1. try_lock
mutex mtx;
unique_lock guard(mtx);guard.try_lock();
  1. release
mutex mtx;
unique_lock guard(mtx);mutex* p_mtx = guard.release();//解除guard与mtx的关联
p_mtx->unlock();//需要自己解锁

锁住代码合适(粒度)。

17.6.4 unique_lock所有权的传递

unique_lock对mutex的所有权可以移动但不能复制。

mutex mtx;
unique_lock<mutex > g1(mtx);
//unique_lock<mutex > g2(mtx);//errorunique_lock<mutex > g2(std::move(g1));
unique<mutex> rtn_unique_lock(){unique_lock<mutex > g(mtx);return g;//会生成临时对象,并调用移动构造函数
}

17.7 单例设计模式共享数据分析、解决与call_once

17.7.1 设计模式简单谈

项目开发经验、模块划分经验等总结起来构成的一系列开发技巧。扩展方便,程序写起来灵活(增加删除模块、功能方便)。

17.7.2 单例设计模式

特殊的类,该类对象只能创建一个。


#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;class MyCAS
{private:MyCAS(){cout << "MyCAS()\n";}~MyCAS(){cout << "~MyCAS()\n";}private:static MyCAS *m_instance;public:void func(){cout << "test" << endl;}static MyCAS *GetInstance(){if (m_instance == nullptr){static CGarhuishou cg;m_instance = new MyCAS();}return m_instance;}private:class CGarhuishou{public:CGarhuishou(){cout << "CGarhuishou()\n";}~CGarhuishou(){if (MyCAS::m_instance){delete MyCAS::m_instance;MyCAS::m_instance = nullptr;}cout << "~CGarhuishou()\n";}};
};MyCAS *MyCAS::m_instance = nullptr;int main()
{{MyCAS::GetInstance()->func();MyCAS * p_a = MyCAS::GetInstance();p_a->func();}cout << "main over" << endl;return 0;
}

17.7.3 单例设计模式共享问题分析、解决


#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;class MyCAS
{private:MyCAS(){cout << "MyCAS()\n";}~MyCAS(){cout << "~MyCAS()\n";}private:static MyCAS *m_instance;static mutex mtx;public:void func(){cout << "test" << endl;}static MyCAS *GetInstance(){if (m_instance == nullptr){unique_lock u(mtx);if (m_instance == nullptr){static CGarhuishou cg;m_instance = new MyCAS();}}return m_instance;}private:class CGarhuishou{public:CGarhuishou(){cout << "CGarhuishou()\n";}~CGarhuishou(){if (MyCAS::m_instance){delete MyCAS::m_instance;MyCAS::m_instance = nullptr;}cout << "~CGarhuishou()\n";}};
};MyCAS *MyCAS::m_instance = nullptr;
mutex MyCAS::mtx;void mythread()
{cout << "begin\n";MyCAS *p_a = MyCAS::GetInstance();p_a->func();cout << "end\n";
}int main()
{{thread t1(mythread);thread t2(mythread);t1.join();t2.join();}cout << "main over" << endl;return 0;
}

17.7.4 std::call_once

保证函数只被执行一次。
once_flag,结构,标记。


#include <iostream>
#include <thread>
#include <mutex>
using namespace std;class MyCAS
{private:MyCAS(){cout << "MyCAS()\n";}~MyCAS(){cout << "~MyCAS()\n";}private:static MyCAS *m_instance;static once_flag g_flag;public:void func(){cout << "test" << endl;}static MyCAS *GetInstance(){if (m_instance == nullptr)call_once(g_flag, CreateInstance);return m_instance;}private:static void CreateInstance(){static CGarhuishou cg;m_instance = new MyCAS();}private:class CGarhuishou{public:CGarhuishou(){cout << "CGarhuishou()\n";}~CGarhuishou(){if (MyCAS::m_instance){delete MyCAS::m_instance;MyCAS::m_instance = nullptr;}cout << "~CGarhuishou()\n";}};
};MyCAS *MyCAS::m_instance = nullptr;
once_flag MyCAS::g_flag;void mythread()
{cout << "begin\n";MyCAS *p_a = MyCAS::GetInstance();p_a->func();cout << "end\n";
}int main()
{{thread t1(mythread);thread t2(mythread);t1.join();t2.join();}cout << "main over" << endl;return 0;
}

17.8 condition_variable、wait、notify_one与notify_all

17.8.1 条件变量condition_variable、wait与notify_one

条件变量用于线程中等待一个条件满足(其它线程通知)时,继续往下执行。
condition_variable,条件相关的类,用于等待一个条件达成。

17.8.1-1.cpp

#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;#define N 5
class A
{public:void inMsgRecvQueue(){for (int i = 0; i < N; i++){unique_lock<mutex> g(m);msgRecvQueue.push_back(i);this_thread::sleep_for(chrono::microseconds(20));}}void outMsgRecvQueue(){int command;for (int i = 0; i < N; i++){bool result = outMsgLULProc(command);if (result)cout << "do something" << endl;elsecout << "empty" << endl;this_thread::sleep_for(chrono::microseconds(10));}}private://双重锁定或者双重检查bool outMsgLULProc(int &command){if (!msgRecvQueue.empty()){unique_lock<mutex> g(m);if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();return true;}}return false;}private:list<int> msgRecvQueue;mutex m;
};int main()
{{A a;thread outObj(&A::outMsgRecvQueue, &a);thread inObj(&A::inMsgRecvQueue, std::ref(a));inObj.join();outObj.join();}cout << "main over" << endl;return 0;
}

17.8.1-2.cpp

#include <iostream>
#include <list>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;#define N 5
class A
{public:void inMsgRecvQueue(){for (int i = 0; i < N; i++){unique_lock<mutex> g(m);msgRecvQueue.push_back(i);g.unlock();//this_thread::sleep_for(chrono::milliseconds(2));cond.notify_one();}}void outMsgRecvQueue(){for (int i = 0; i < N; i++){unique_lock<mutex> g(m);// cond.wait(g)等价于cond.wait(g, []{ return false; });// lambda表达式返回true,wait直接返回;// lambda表达式返回false,wait将解锁互斥并堵塞到这行,直到其它线程调用notify。// wait第二次参数是个可调用对象//[this|&]()或者[this|&]cond.wait(g, [&]{ return !msgRecvQueue.empty(); });int command = msgRecvQueue.front();//肯定有数据msgRecvQueue.pop_front();g.unlock(); // unique_lock可以随时解锁,以免锁住太长时间。cout << "do something" << endl;this_thread::sleep_for(chrono::milliseconds(1));}}private:list<int> msgRecvQueue;mutex m;condition_variable cond;
};int main()
{{A a;thread inObj(&A::inMsgRecvQueue, std::ref(a));thread outObj(&A::outMsgRecvQueue, &a);outObj.join();inObj.join();}cout << "main over" << endl;return 0;
}

17.8.2 wait与notify思考

notify不能确保wait一定拿到锁,notify后wait线程不一定唤醒,notify线程可能又抢到锁。

无wait执行时,notify无任何效果。

17.8.3 notify_all

notify_one()只唤醒一个wait线程,notify_all()唤醒所有wait线程。

#include <iostream>
#include <list>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;#define N 5
class A
{public:void inMsgRecvQueue(int n){for (int i = 0; i < N; i++){{unique_lock<mutex> g(m);msgRecvQueue.push_back(i + 1);// g.unlock();}this_thread::sleep_for(chrono::milliseconds(2));cond.notify_all();}//生产结束,消息队列放入0unique_lock<mutex> g(m);for (int i = 0; i < n; i++)msgRecvQueue.push_back(0);g.unlock();cond.notify_all();cout << "inMsgRecvQueue over" << endl;}void outMsgRecvQueue(){while (true){unique_lock<mutex> g(m);cond.wait(g, [&]{ return !msgRecvQueue.empty(); });int command = msgRecvQueue.front();msgRecvQueue.pop_front();g.unlock();if (command == 0) //消息队列放入0,跳出循环break;cout << "do something" << endl;this_thread::sleep_for(chrono::milliseconds(1));}cout << "outMsgRecvQueue over" << endl;}private:list<int> msgRecvQueue;mutex m;condition_variable cond;
};int main()
{{A a;int n = 5;vector<thread> threads(n);for (int i = 0; i < n; i++)threads[i] = thread(&A::outMsgRecvQueue, std::ref(a));thread inObj(&A::inMsgRecvQueue, std::ref(a), n);inObj.join();for (int i = 0; i < n; i++)threads[i].join();}cout << "main over" << endl;return 0;
}

17.9 async、future、packaged_task和promise

17.9.1 std::async和std::future创建后台任务并返回值

  1. std::async和std::future的用法
    std::async是一个函数模板,用来启用一个异步任务(自动创建一个新线程【有时不会】并开始执行对应的线程入口函数),返回一个std::future对象(类模板,含有线程入口函数的返回结果)。
#include <iostream>
#include <thread>
#include <future>
using namespace std;int mythread(){cout<<this_thread::get_id()<<" start"<<endl;this_thread::sleep_for(chrono::microseconds(10));cout<<this_thread::get_id()<<" end"<<endl;return 5;
}int main()
{{future<int> result = async(mythread);result.wait();//只等待线程返回,本身不返回结果。cout<<result.get()<<endl;//卡在这里,等待子线程结束,只能调用一次。}cout << "main over" << endl;return 0;
}
#include <iostream>
#include <thread>
#include <future>
using namespace std;class A
{public:int mythread(int n){cout << n << endl;cout << this_thread::get_id() << " start" << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;return 5;}
};int main()
{{A a;int n = 12;future<int> result = async(&A::mythread, &a, n);result.wait();                //只等待线程返回,本身不返回结果。cout << result.get() << endl; //卡在这里,等待子线程结束,只能调用一次。}cout << "main over" << endl;return 0;
}
  1. std::async额外参数详解

(1)launch::deferred
线程入口函数执行延迟到wait货get函数调用(此时未创建新线程),无调用则线程不执行。

auto result = async(launch::deferred, &A::mythread, &a, n);

(2)launch::async
创建时就立即执行(异步任务在新线程执行)。

auto result = async(launch::deferred, &A::mythread, &a, n);

(3)launch::deferred | launch::async
系统自行决定同步(无新线程)或异步(创建新线程)。

(4)无额外参数,同(3)

  1. std::async和std::thread的区别
    thread不容易拿到线程返回值,一般通过指针;
    async通过future拿到。
    thread创建线程太多,可能失败或崩溃;async一般不会。

  2. std::async不确定性问题的解决
    同步或异步。

17.9.2 std::packaged_task

类模板,模板参数是各种可调用对象,packaged_task将对象包装起来,方便作为线程入口函数使用。

#include <iostream>
#include <thread>
#include <future>
using namespace std;int mythread(int n)
{cout << n << endl;cout << this_thread::get_id() << " start" << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;return 5;
}int main()
{{packaged_task<int(int)> mypt(mythread);thread t(ref(mypt), 1);future<int> result = mypt.get_future();cout<<result.get()<<endl;t.join();}cout << "main over" << endl;return 0;
}
#include <iostream>
#include <thread>
#include <future>
using namespace std;int main()
{{packaged_task<int(int)> mypt([](int n) {       cout << n << endl;cout << this_thread::get_id() << " start" << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;return 5; });thread t(ref(mypt), 1);future<int> result = mypt.get_future();cout << result.get() << endl;t.join();}cout << "main over" << endl;return 0;
}

packaged_task包装起来的对象可以直接调用,packaged_task对象也是一个可调用对象。

#include <iostream>
#include <thread>
#include <future>
using namespace std;int main()
{cout << "main id " << this_thread::get_id()  << endl;{packaged_task<int(int)> mypt([](int n) {       cout << n << endl;cout << this_thread::get_id() << " start" << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;return 5; });//thread t(ref(mypt), 1);mypt(105);future<int> result = mypt.get_future();cout << result.get() << endl;//t.join();}cout << "main over" << endl;return 0;
}
#include <iostream>
#include <thread>
#include <future>
#include <vector>
using namespace std;int main()
{cout << "main id " << this_thread::get_id()  << endl;{packaged_task<int(int)> mypt([](int n) {       cout << n << endl;cout << this_thread::get_id() << " start" << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;return 5; });vector<packaged_task<int(int)>> mytasks;mytasks.push_back(std::move(mypt));packaged_task<int(int)> mypt2;auto iter = mytasks.begin();mypt2=std::move(*iter);mytasks.erase(iter);mypt2(105);future<int> result = mypt2.get_future();cout << result.get() << endl;}cout << "main over" << endl;return 0;
}

17.9.3 std::promise

类模板,某个线程中赋值,其它线程中取值。

#include <iostream>
#include <thread>
#include <future>
#include <vector>
using namespace std;void mythread(promise<int> &tmp, int n)
{cout << n << endl;cout << this_thread::get_id() << " start" << endl;tmp.set_value(n*10);this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;
}int main()
{cout << "main id " << this_thread::get_id() << endl;{promise<int> prog;thread t(mythread, std::ref(prog), 11);future<int> result = prog.get_future();cout << result.get() << endl;t.join();}cout << "main over" << endl;return 0;
}
#include <iostream>
#include <thread>
#include <future>
#include <vector>
using namespace std;void mythread(promise<int> &tmp, int n)
{cout << n << endl;cout << this_thread::get_id() << " start" << endl;tmp.set_value(n * 10);this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;
}void mythread2(future<int> &tmp)
{cout << tmp.get() << endl;//get只能调用一次cout << this_thread::get_id() << " start" << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;
}int main()
{cout << "main id " << this_thread::get_id() << endl;{promise<int> prog;thread t1(mythread, std::ref(prog), 11);t1.join();future<int> result = prog.get_future();// cout << result.get() << endl;//get只能调用一次thread t2(mythread2, std::ref(result));t2.join();}cout << "main over" << endl;return 0;
}

17.10 future其它成员函数、shared_future与atomic

17.10.1 std::future其它成员函数

#include <iostream>
#include <thread>
#include <future>
#include <vector>
using namespace std;int mythread()
{cout << this_thread::get_id() << " start" << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;return 5;
}int main()
{cout << "main id " << this_thread::get_id() << endl;{future<int> result = async(mythread);// future<int> result = async(launch::deferred, mythread);future_status status = result.wait_for(chrono::microseconds(8));switch (status){case future_status::timeout: //超时未执行完cout << result.get() << endl;break;case future_status::ready: //执行完cout << result.get() << endl;break;case future_status::deferred: //延迟未执行cout << result.get() << endl;break;}}cout << "main over" << endl;return 0;
}

17.10.2 续谈std::async不确定性问题

//异步执行
future<int> result = async(launch::async, mythread);

17.10.3 std::shared_future

future移动语义,get只能调用一次;
shared_future数据复制非转移,get可调用多次。

#include <iostream>
#include <thread>
#include <future>
#include <vector>
using namespace std;int mythread(int n)
{cout << n << endl;cout << this_thread::get_id() << " start" << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;return 5;
}void mythread2(shared_future<int> &tmp)
{cout << this_thread::get_id() << " start" << endl;cout << tmp.get() << endl;this_thread::sleep_for(chrono::microseconds(10));cout << this_thread::get_id() << " end" << endl;
}int main()
{cout << "main id " << this_thread::get_id() << endl;{packaged_task<int(int)> mypt(mythread);thread t1(ref(mypt), 1);t1.join();future<int> result = mypt.get_future();bool ifcanget = result.valid(); //未调用过get,true// auto mythreadresult = result.get();//只能get一次// ifcanget = result.valid();//get后,false// result未调用get,将result内容放在shared_future中,future空了shared_future<int> result_s(move(result));// shared_future<int> result_s(result.share());//同上ifcanget = result.valid();   // false,furure空了ifcanget = result_s.valid(); // true,shared_future有内容//可以多次调用getauto mythreadresult = result_s.get();mythreadresult = result_s.get();thread t2(mythread2, ref(result_s));t2.join();}cout << "main over" << endl;return 0;
}
int main()
{cout << "main id " << this_thread::get_id() << endl;{packaged_task<int(int)> mypt(mythread);thread t1(ref(mypt), 1);t1.join();shared_future<int> result_s(mypt.get_future());// shared_future<int> result_s(result.share());//同上//可以多次调用getauto mythreadresult = result_s.get();mythreadresult = result_s.get();thread t2(mythread2, ref(result_s));t2.join();}cout << "main over" << endl;return 0;
}

17.10.4 原子操作std::atomic

一种不需要用到互斥量加锁(无锁)技术的多线程并发编程方式,在多线程中不会被打断的程序执行片段,效率更高。
互斥量加锁针对一个代码段(几行代码),原子操作针对一个变量。
原子操作指不可分割的操作,操作的状态要么完成,要么没完成。

std::atomic<int> count = 0;
void mythread(){for(int i=0; i<1000; i++){count++;count += 1;//count = count + 1;//非原子操作}
}
++、--、+=、-=、&=、|=、^=等简单运算符是原子操作
std::atomic<bool> flag = false;

17.11 Windows临界区与其它各种mutex互斥量

17.11.1 Windows临界区

#include <windows.h>#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;#define __WINDOWSLJQ__#define N 5
class A
{public:A(){#ifdef __WINDOWSLJQ__InitializeCriticalSection(&winsec);
#endif}virtual ~A(){#ifdef __WINDOWSLJQ__DeleteCriticalSection(&winsec);
#endif}public:void inMsgRecvQueue(){for (int i = 0; i < N; i++){#ifdef __WINDOWSLJQ__EnterCriticalSection(&winsec);msgRecvQueue.push_back(i);LeaveCriticalSection(&winsec);
#elsemtx.lock();msgRecvQueue.push_back(i);mtx.unlock();
#endif}}void outMsgRecvQueue(){int command;for (int i = 0; i < N; i++){bool result = outMsgLULProc(command);if (!result)cout << "empty" << endl;elsecout << "do something" << endl;}}private:bool outMsgLULProc(int &command){#ifdef __WINDOWSLJQ__EnterCriticalSection(&winsec);if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();LeaveCriticalSection(&winsec);return true;}LeaveCriticalSection(&winsec);
#elsemtx.lock();if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();mtx.unlock();return true;}mtx.unlock();
#endifreturn false;}private:list<int> msgRecvQueue;mutex mtx;
#ifdef __WINDOWSLJQ__CRITICAL_SECTION winsec;
#endif
};int main()
{{A a;thread outObj(&A::outMsgRecvQueue, &a);thread inObj(&A::inMsgRecvQueue, std::ref(a));inObj.join();outObj.join();}cout << "main over" << endl;return 0;
}

17.11.2 多次进入临界区试验

临界区,需要在多线程编程中进行保护的共享数据相关的代码行(区域)。

#include <windows.h>#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;#define __WINDOWSLJQ__#define N 5
class A
{public:A(){#ifdef __WINDOWSLJQ__InitializeCriticalSection(&winsec);
#endif}virtual ~A(){#ifdef __WINDOWSLJQ__DeleteCriticalSection(&winsec);
#endif}public:void inMsgRecvQueue(){for (int i = 0; i < N; i++){#ifdef __WINDOWSLJQ__EnterCriticalSection(&winsec); //进入临界区EnterCriticalSection(&winsec); //调用两次msgRecvQueue.push_back(i);LeaveCriticalSection(&winsec); //离开临界区LeaveCriticalSection(&winsec); //也要调用两次
#elsemtx.lock();// mtx.lock();//errormsgRecvQueue.push_back(i);mtx.unlock();// mtx.unlock();//error
#endif}}void outMsgRecvQueue(){int command;for (int i = 0; i < N; i++){bool result = outMsgLULProc(command);if (!result)cout << "empty" << endl;elsecout << "do something" << endl;}}private:bool outMsgLULProc(int &command){#ifdef __WINDOWSLJQ__EnterCriticalSection(&winsec);if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();LeaveCriticalSection(&winsec);return true;}LeaveCriticalSection(&winsec);
#elsemtx.lock();if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();mtx.unlock();return true;}mtx.unlock();
#endifreturn false;}private:list<int> msgRecvQueue;mutex mtx;
#ifdef __WINDOWSLJQ__CRITICAL_SECTION winsec;
#endif
};int main()
{{A a;thread outObj(&A::outMsgRecvQueue, &a);thread inObj(&A::inMsgRecvQueue, std::ref(a));inObj.join();outObj.join();}cout << "main over" << endl;return 0;
}

17.11.3 自动析构技术

RAII(Resource Acquisition Is Initialization)类与对象,资源获取即初始化。构造函数中初始化资源,析构函数中释放资源。只能指针、容器等用到。

#include <windows.h>#include <iostream>
#include <list>
#include <thread>
#include <mutex>
using namespace std;#define __WINDOWSLJQ__class CWinLock
{public:CWinLock(CRITICAL_SECTION *p) : critical(p){EnterCriticalSection(critical); //进入临界区}~CWinLock(){LeaveCriticalSection(critical); //离开临界区}private:CRITICAL_SECTION *critical;
};#define N 5
class A
{public:A(){#ifdef __WINDOWSLJQ__InitializeCriticalSection(&winsec);
#endif}virtual ~A(){#ifdef __WINDOWSLJQ__DeleteCriticalSection(&winsec);
#endif}public:void inMsgRecvQueue(){for (int i = 0; i < N; i++){#ifdef __WINDOWSLJQ__CWinLock wlock1(&winsec);CWinLock wlock2(&winsec); //调用多次没问题msgRecvQueue.push_back(i);
#elselock_guard<mutex> g1(mtx);// lock_guard<mutex> g2(mtx);//errormsgRecvQueue.push_back(i);
#endif}}void outMsgRecvQueue(){int command;for (int i = 0; i < N; i++){bool result = outMsgLULProc(command);if (!result)cout << "empty" << endl;elsecout << "do something" << endl;}}private:bool outMsgLULProc(int &command){#ifdef __WINDOWSLJQ__EnterCriticalSection(&winsec);if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();LeaveCriticalSection(&winsec);return true;}LeaveCriticalSection(&winsec);
#elsemtx.lock();if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();mtx.unlock();return true;}mtx.unlock();
#endifreturn false;}private:list<int> msgRecvQueue;mutex mtx;
#ifdef __WINDOWSLJQ__CRITICAL_SECTION winsec;
#endif
};int main()
{{A a;thread outObj(&A::outMsgRecvQueue, &a);thread inObj(&A::inMsgRecvQueue, std::ref(a));inObj.join();outObj.join();}cout << "main over" << endl;return 0;
}

17.11.4 recursive_mutex递归的独占互斥量

允许同一个线程多次调用同一个recursive_mutex的lock成员函数,mutex的lock不允许连续多次调用。

class A{public:void test1(){lock_guard<recursive_mutex > g(mtx);}void test2(){lock_guard<recursive_mutex > g(mtx);test1();}
private:recursive_mutex mtx;
};

17.11.5 带超时的互斥量std::timed_mutx和std::recursive_timed_mutex

std::timed_mutx带超时功能的独占互斥锁;std::recursive_timed_mutex带超时功能的递归的独占互斥锁。

std::timed_mutx mtx;
for(int i=0; i<1000; i++){chrono::milliseconds timeout(100);if(mtx.try_lock_for(timeout)){//if(mtx.try_lock_until(chrono::steady_clock::now() + timeout)){//....mtx.unlock();}else{this_thread::sleep_for(chrono::milliseconds(300));}
}

17.12 补充知识、线程池浅谈、数量谈与总结

17.12.1 知识点补充

  1. 虚假唤醒

wait线程醒来后没有实际可供处理的数据,叫虚假唤醒。
比如,push_back一条数据,调用多次notify_one,或者多个线程取数据,总有个线程唤醒后,但队列中没有数据可处理。

一般while替换if。

#include <iostream>
#include <list>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;#define N 5
class A
{public:void inMsgRecvQueue(int n){for (int i = 0; i < N; i++){{unique_lock<mutex> g(m);msgRecvQueue.push_back(i + 1);// g.unlock();}this_thread::sleep_for(chrono::milliseconds(2));cond.notify_all();}//生产结束,消息队列放入0unique_lock<mutex> g(m);for (int i = 0; i < n; i++)msgRecvQueue.push_back(0);g.unlock();cond.notify_all();cout << "inMsgRecvQueue over" << endl;}void outMsgRecvQueue(){while (true){unique_lock<mutex> g(m);//cond.wait(g, [&]{ return !msgRecvQueue.empty(); });while(msgRecvQueue.empty())cond.wait(g);int command = msgRecvQueue.front();msgRecvQueue.pop_front();g.unlock();if (command == 0) //消息队列放入0,跳出循环break;cout << "do something" << endl;this_thread::sleep_for(chrono::milliseconds(1));}cout << "outMsgRecvQueue over" << endl;}private:list<int> msgRecvQueue;mutex m;condition_variable cond;
};int main()
{{A a;int n = 5;vector<thread> threads(n);for (int i = 0; i < n; i++)threads[i] = thread(&A::outMsgRecvQueue, std::ref(a));thread inObj(&A::inMsgRecvQueue, std::ref(a), n);inObj.join();for (int i = 0; i < n; i++)threads[i].join();}cout << "main over" << endl;return 0;
}
  1. atomic的进一步理解

atomic禁用拷贝构造函数和复制赋值运算符。

atomic<int> atm1;
atm1 = 0;atomic<int> atm3;
//atm3 = atm1;//erroratomic<int> atm5(atm1.load());
atm5.store(12);
atm5 = 12;

17.12.2 线程池浅谈

程序中偶尔达成某种条件时就创建一个线程,不够稳定。
线程池将一堆线程放在一起,进行统一的管理调度。

17.12.3 线程创建数量谈

一般2000各左右线程就是极限,一个进程线程数不要超过500,200以内最好。

《C++新经典》第17章 并发与多线程相关推荐

  1. C++新经典——C++从入门到精通

    目录 专栏目的 章节目录: 开发环境: 专栏目的 博主开这一个专栏博客的目的是复习巩固博主之前学的C++知识点, 也就是 王健伟老师的C++书籍 <C++新经典>读书笔记 后续会将所有的部 ...

  2. 《C++新经典Linux C++通信架构实战》第2章 进入Nginx之门

    <C++新经典Linux C++通信架构实战>第2章 进入Nginx之门 2.1 Nginx简介.选择理由.安装和使用 2.1.1 Nginx简介 2.1.2 为什么选择Nginx 2.1 ...

  3. 《C++新经典》第1章 C/C++语言

    <C++新经典>第1章 C/C++语言 C语言最突出特点: 效率高. 灵活性.可以直接访问物理地址(操作硬件),进行位运算. C++语言特性:封装性.继承性.多态性. Visual Stu ...

  4. 《C++新经典Linux C++通信架构实战》第1章 课程介绍

    <C++新经典Linux C++通信架构实战>第1章 课程介绍 1.1 本书内容详细介绍 1.1.1 内容总述 1.1.2 为什么选择Linux操作系统平台 1.1.3 讲解规划和学习建议 ...

  5. 读书笔记:《流畅的Python》第17章 使用future处理并发

    # 第17章 使用future处理并发""" 内容提要:concurrent.futures模块future的概念:是一种对象,表示异步执行的操作是concurrent. ...

  6. 复现经典:《统计学习方法》​第17章 潜在语义分析

    第17章 潜在语义分析 本文是李航老师的<统计学习方法>一书的代码复现.作者:黄海广 备注:代码都可以在github中下载.我将陆续将代码发布在公众号"机器学习初学者" ...

  7. 第 17 章 垃圾回收器

    第 17 章 垃圾回收器 1.GC 分类与性能指标 1.1.垃圾回收器概述与分类 垃圾回收器概述 垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商.不同版本的JVM来实现. 由于JDK的版本处于 ...

  8. 第二十四章 并发编程

    第二十四章 并发编程 爱丽丝:"但是我不想进入疯狂的人群中" 猫咪:"oh,你无能为力,我们都疯了,我疯了,你也疯了" 爱丽丝:"你怎么知道我疯了&q ...

  9. 第17章:图像分割提取

    第17章:图像分割提取 一.用分水岭算法实现图像分割提取: 1. 算法原理: 2. 相关函数介绍: (1) 形态学函数回顾: (2) 距离变换函数distanceTransform: (3) 确定未知 ...

  10. 【17】 强化学习 17章 前沿技术

    文章目录 名词 离轨策略 折扣过程 折扣系数 价值函数 广义策略迭代(4.6节)或者"行动器一评判器"算法 正文 17.1 广义价值函数和辅助任务 1.广义价值函数是什么? 2.辅 ...

最新文章

  1. Spark2.2.0分布式集群安装(StandAlone模式)
  2. Vue中绑定值与字符串拼接以及结合三目表达式实现是否为空判定的使用
  3. Python scrapy爬取京东,百度百科出现乱码,解决方案
  4. EDP项目结构规范心得
  5. java下载的文件不完整_JAVA 解决FTP下载文件不完整问题
  6. Makefile.am编写规则
  7. 十六进制转二进制原理
  8. 常用的评论/帖子/文章排序算法四(牛顿冷却定律)
  9. 什么副业可以月赚1万元?做什么副业可以月入上万?
  10. C++ 智能指针(二) std::unique_ptr
  11. 使用Stream distinct()去重失效问题
  12. 冷门但实用的Word技巧
  13. 人民币终于大幅度贬值说明什么
  14. 数据分析 互联网 常用缩写大全(未完待续)
  15. 织梦 php 调用栏目,织梦dedecms如何调用当前栏目文章数
  16. Springboot 一文搞懂AOP面向切面编程
  17. 淘宝虚拟产品自动发货软件
  18. 矩阵光学 matlab,矩阵光学知识讲义.doc
  19. 2020年中国高压变频器行业现状分析,变频用变压器具有较大的增长空间及快速发展潜力「图」
  20. jpcsp源码解读7:HLE

热门文章

  1. ZXing设置前置摄像头扫描
  2. 小游戏系列——猜数字游戏
  3. 大学四年因为读了这13本书,我成了别人眼中的大神!
  4. 漂泊的旅途,云淡风轻
  5. 【qq机器人】定时发送消息大全
  6. 山东农业大学计算机考研资料汇总
  7. Python 基于 uiautomator2 实现《全民开喵铺》自动收币,自动签到,自动浏览得喵币
  8. vb服务器获取ftp文件,vb获取ftp服务器文件时间戳
  9. 在8X8的棋盘上分布着n个骑士,他们想约在某一个格中聚会。骑士每天可以像国际象棋中的马那样移动一次,可以从中间像8个方向移动(当然不能走出棋盘),请计算n个骑士的最早聚会地点和要走多少天。要求尽早聚会
  10. Ubuntu安装N卡驱动