并发,线程,进程的基本概念
并发
并发:两个或多个任务(独立的活动)同时发生(进行),
目的:可以执行更多的任务,提高性能
thread 类
thread 创建线程有三种方式,
(1) 创建线程传递函数入口地址
(2) 创建线程传递仿函数对象
(2) 创建线程传递lambda 对象
join(); //阻塞等待对应的线程结束,执行join的线程才会继续往下执行
detach(); //线程分离,让detach() 对应的线程脱离 执行 detach() 的线程。
joinable(); //判断 该对象的线程是否可以等待, 返回 true 表示可以等待,否则不可以等待(之前调用已经 detach() )。
this_thread 命名空间
this_thread::get_id() ; //获取线程 ID
this_thread::sleep_for(); //c++标准睡眠平台
ref() 函数
thread myThread(func, std::ref(obj)); // func 为函数入口, obj 为 对象,则表示不需要拷贝obj对象到新的线程中,而是在新线程中 创建一个引用对象,引用obj对象。
函数入口
注意点: thread myThread(func, arg, pMyBuf); // func 为函数入口, agr 为值传递,pMyBuf为指针传递
// 在该过程中, 不管是引用传递还是值传递,都会在新线程中拷贝一份对应的对象。 但是 pMyBuf 指针传递则是传递 指针值,所有 我们在指针传递过程中不能传递 指针指向 栈内存的地址( char buf[] 这种为栈中的内存, string 类的字符串不在栈中,如果想要将栈中的字符串传递到新线程中,可以在函数入口创建 string 的临时对象,”不要让编译器自动隐士转换,会存在可能栈中的buf释放了,才进行隐士转换问题“)
总结:创建对象的过程中,主线程先将参数a1对象拷贝一份a2对象放到新线程中(a2诞生在主线程),等到到新线程入口函数执行后,新线程将参数a2传递给函数入口参数,但是入口函数执行结束后a2对象的析构函数是在新线程执行(a2结束在新线程)。
仿函数对象
注意点:thread myThread(functor); // functor 为仿函数对象,
//在该过程中,创建新线程,在新线程中会存在 functor 对象的拷贝,所有functor对象内部数据最好不要用到指针和引用
类的成员函数作为入口
thread myThread( &A::fun, a, 10); // fun 为A类的成员函数, a 为 A类的对象, 10 为成员函数的参数
lambda对象
auto lambda = [] { cout << "lambda"<< endl;};
thread myThread(lambda );
全局变量问题
全局变量诞生在main()函数执行之前,当main()直接结束会结束全局变量的声明周期。
在多线程中,如果主线程结束生命周期后,其他线程不能使用全局变量。
mutex类 & lock_guard模板类
mutex
mutex用来对共享资源得互斥,lock(), unlock()必须成对存在
mutex myMutex;
myMutex.lock();
....保护数据
myMutex.unlock();
lock_guard
{
// lockGuard对象一旦创建,内部执行 lock()
lock_guard<mutex> lockGuard(myMutex); //myMutex 为互斥量对象
.... 保护数据
//lockGuard 结束声明周期则会执行 unlock();
//所以作用域的使用,是 lock_guard的关键
}
lock() 函数模板
lock() 可以锁住多个互斥量,导致多个互斥量不会死锁的问题,
// 其原理应该是能将多个互斥量都加锁,才会全部加锁,不然一个都不加锁。
mutex mutex1, mutex2;
lock(mutex1, mutex2); //该函数接受不定参数
... 保护数据
mutex1.unlock(); //解锁要自己解锁
mutex2.unlock();
lock() 函数 与 lock_guard 模板类配合使用
mutex mutex1, mutex2;
{
lock(mutex1, mutex2); //该函数接受不定参数
lock_guard<mutex> lockGuard1(mutex1, std::adopt_lock); // std::adopt_lock 表示创建不加锁
lock_guard<mutex> lockGuard2(mutex2, std::adopt_lock);
... 保护数据
}
unique_lock 类模板
{
mutex mutex1;
unique_lock<mutex> uniqueLock(mutex1); // unique_lock 与 lock_guard 用法相似
...保护数据
}
第二参数有:
std::adopt_lock 创建不加锁,前提是mutex提前lock();
std::try_to_lock 尝试加锁,不管加不加锁该对象都会创建成功,通过 owns_lock() 判断是否加锁成功。
std::defer_lock 初始化一个没有加锁的mutex,
函数
lock();
unlock();
try_lock();
release(); //返回对象指针,将mutex 和 unique_lock 解除。
所有权的传递
unique_lock 不可以复制,但是可以移动,移动的过程不会再次加锁
单例共享数据
单例模式
class MyA
{
public:
volatile static MyA* createSingle(int i) {
if (single == nullptr) {
lock_guard<mutex> lockGuard(myMutex);
if (single == nullptr) {
single = new MyA(i);
//程序退出,删除单例对象使用到
static DelMyA s_delMya;
}
}
return single;
}
class DelMyA { //类中嵌套对象,用来释放对象,前提条件是 主线程退出,其他线程不能使用单例对象
public:
~DelMyA() {
if (MyA::single != nullptr) {
delete MyA::single; // 类内得数据可以相互访问
MyA::single = nullptr;
cout << "释放单例程对象内存" << endl;
}
}
};
int getSignleData() volatile {
return m_i;
}
private:
MyA(int i) : m_i(i) { cout << "构造函数执行" << endl; }
int m_i;
volatile static MyA *single;
static mutex myMutex;
};
mutex MyA::myMutex;
volatile MyA *MyA::single = nullptr;
call_once() 函数
void (call_once)(once_flag& _Flag, _Fn&& _Fx, _Args&&... _Ax);
保证调用的函数只调用一次。
比互斥量消耗资源更少。
condition_variable 类
mutex myMutex;
condition_variable cond;
......
函数等待条件:
{
unique_lock<mutex> uniqueLock(myMutex);
// 带函数的wait用法
cond.wait( uniqueLock, [this] {
if(this->flag)
return true;
return false;
});
// 不带函数的wait 用法
cond.wait(uniqueLock);
...执行等待条件成立
}
将等待条件唤醒
{
unique_lock<mutex> uniqueLock(myMutex);
...执行条件成立的动作
cond.notify_one(); 或 cond.notify_all();
}
wait
1.不带函数时,将互斥量解锁,阻塞等待条件通知,
2. 带函数时,当函数返回false 时,表示条件不成立,将互斥量解锁,阻塞等待条件通知,函数返回 true 时,表示条件成立,则直接往下执行。
3.收到通知唤醒后尝试对互斥量进行加锁,加锁成功后(不带函数(退出wait(),往下执行) 或是带函数(执行 2 步骤)),往下执行;否则,继续阻塞等待条件通知。
notify_one 和 notify_all
notify_one() 唤醒正在阻塞的线程,但是当没有线程阻塞,notify_one() 执行没有效果,该函数是将阻塞等待的线程消息队列出队一个线程,将其唤醒,如果阻塞等待的消息队列没有线程,也就是不会有线程被唤醒。
notify_all() 是将所有阻塞等待的线程消息队列出队,依次唤醒。
async 函数模板 和 future 类模板
async()
async() 参数的传递以及线程函数入口参数的传递,与 thread 类创建一致。
1.future<type> 的 type 与 线程入口函数的返回要一致。
2.future::get()会阻塞等待线程执行结束返回结果。
3.future::wait() 阻塞等待线程返回,但是不在意返回值。
4.不调用 future成员函数,该程序也会卡在 future对象的析构函数中,等待绑定的线程结束
当async() 函数的第一个参数不是 线程入口函数时,而是 std::launch::deferred ,
1.当与async函数绑定的future 对象调用 get() 或是 wait()时,入口函数才会执行。
2.注意是入口函数,不是线程入口函数,当async函数的第一个参数是 std::launch::deferred 时,不会创建新线程,而是future对象调用 get() 或是 wait() 时,间接调用入口函数。
3.当 future 对象不调用 get() 或是 wait() 函数,future对象析构函数不会启动入口函数。
future

packaged_task 类模板
包装函数当线程入口

包装lambda表达式当线程入口

promise 类模板
我们能够在某个线程中它他赋值,然后我们可以再其他线程中,把这个值取出来使用。

async, packaged_task, promise 和 future
async,packaged_task, promise 的使用都是通过 future 类
async 是 创建一个异步操作,该操作的返回值通过 future 获取,异步操作结束future才能获取到。
packaged_task 可以打包多个任务,任务返回值还是通过 future 获取,多个任务操作结束future 才能获取到。
promise 的作用是消除了 任务返回时才可以的缺点,只要向 promise 设置返回值,future就可以获取到结果,任务还可以继续往下执行
shared_future 类模板
future.get() 获取数据时是使用 move() ,也就是只能获取一次
shared_future.get() 获取数据是拷贝数据,以便多个线程使用该值。
atomic
原子操作:在多线程中不会被打断的程序执行片段
原子操作是一种不需要加锁(无锁)技术的多线程,原子操作的效率比加锁性能更高。
注意:互斥量针对的是代码段,原子操作是针对一个变量。
std::atomic 只针对内嵌变量,一般用于多线程统计。
atomic 支持的运算符
++, --, +=, -=, &=, |=, ~=,是支持

recursive_mutex
mutex 不可以多次lock,
recursive_mutex 可以多次lock。
recursive_mutex 和以个 lock_guard, unique_lock 配合使用
timed_mutex 和 recursive_timed_mutex
带超时功能的独占互斥量和带超时的递归的独占互斥量
timed_mutex ,recursive_timed_mutex和以个 lock_guard, unique_lock 配合使用,但是超时加锁超时得自己释放锁
try_lock_for(arg); // arg 表示得是一段时间
try_lock_until(arg) // arg 表示是的是某个时间点。
