C++98/11 多线程与高并发
基本概念:
并发:两个或多个任务同时进行;一个程序同时执行多个独立的任务
并发的假象:cpu的上下文切换
使用并发的原因:同时干多件事,提高效率
可执行文件:.exe文件,可以在Windows下运行,Linux下,./文件名;
进程:一个可执行文件运行起来了,即创建进程
线程:每个进程,都有一个主线程,这个主线程唯一的,也就是一个进程中只有一个主线程,进程运行起来,主线程也运行起来
线程:用来执行代码的,一条代码的执行(通路)道路;除了主线程外,可以创建其他线程,每创建一个线程,在同一时刻就可以多干一件事
线程并非越多越好,每开辟一个线程,都需要有一个独立的堆栈空间(1M),线程切换会耗费时间
并发的实现:两个或多个任务同时发生
实现并发的手段:1.多个进程实现并发
2.单个进程中创建多个线程实现并发
多进程并发:多个进程同时执行
进程之间的通信:同一台电脑上:管道,文件,共享内存,消息队列
不同电脑:socket通信技术
多线程并发:单个进程中,创建多个线程
线程:轻量级进程,同一个进程中,所有线程共享内存,,所以线程开销远小于进程,但是带来内存数据不一致问题
线程启动:
join()函数:主线程阻塞,等待子线程执行完,当子线程执行完毕,主线程继续执行,直到结束
detach()函数:主线程与子线程同时执行,子线程就会驻留在后台运行,被C++运行时库接管,当子线程执行完毕,运行时库处理该线程相关资源(守护进程)
一旦调用detach(),就不能再用join()
joinable():判断是否成功使用join()或detach(),成功返回true
其他创建线程方法:用类对象(可调用对象)
一旦调用detach(),主线程结束,创建的对象也不在了,但是复制的对象在子线程中依然存在,只要不是指针,引用,就不会产生问题
传递临时对象作为参数:char字符数组作为参数时,转为string对象;只要用临时构造的类对象作为参数传递给线程,那么就一定能在主线程执行完毕时把参数传给线程,保证子线程的执行
如果传递对象,避免隐式转换,建议用引用来接
若传递int这种简单的类型,建议用值传递
建议不使用detach(),用join()避免局部变量失效导致线程对内存的非法引用
线程ID:一个线程只有一个id,std::this_thread::get_id()获取线程id函数
传递类对象、智能指针作为线程参数:引用std::ref() == &;
创建和等待多个线程:多个线程的执行顺序是乱的,跟操作系统内部对线程的调度有关
主线程等待子线程全部结束,最后主线程结束,用join()
数据共享问题分析:只读的数据是安全的,有读有写的数据没有特别处理,程序一定崩溃
互斥量:类对象,多线程尝试用lock()加锁,只有一个线程能锁住成功,如果没锁成功,那么流程卡在lock()不断尝试去锁
先lock(),操作共享数据,再unlock()
lock()与unlock()成对使用
std::lock_guard类模板可以避免忘记unlock()而自动解锁(一旦使用lock_guard(),unlock()和lock()都不能使用)
lock_guard(),在构造函数中执行mutex::lock(),在析构函数中执行mutex::unlock()
死锁:线程A执行时,先加金锁,再加银锁
线程B执行时,先加银锁,,再去要金锁
线程A在等银锁,线程B在等金锁,因此产生死锁
死锁解决方案:保证互斥量上锁的顺序一致就不会产生死锁
std::lock()函数模板:用来处理多个互斥量,一次锁住两个或两个以上的互斥量,不会产生因为锁的顺序产生死锁问题,要么两个都锁住,要么都不锁,如果只锁住一个,则立即解锁
std::lock_guard<std::mutex> 名称(my_mutex,std::adopt_lock)不需要手动解锁
unique_lock类模板:用法上比lock_guard()灵活,但是效率差一点,占用内存多一点
std::adopt_lock(),表示已经被lock(),所以使用前先lock()
std::try_to_lock():尝试用lock()去锁定mutex,但如果没有锁成功,就会返回,不会阻塞在那,前提是不能用lock()
std::defer_lock():初始化了一个没有加锁的mutex
std::defer_lock()成员函数:release()使unique_lock()与mutex分离,需要自己负责mutex的unlock()
锁住代码的多少称为粒度,选择合适的粒度,能够提高运行效率
单例模式:整个项目中,某个或某些对象只能创建一个
单例设计模式共享数据问题分析:如何在我们自己创建的线程中而不是主线程中创建一个单例类对象
两个线程同一入口函数,并行执行时,两个线程会不定时交替执行,用互斥量或std::call_once()解决
std::call_once():能够保证函数()只被调用一次,具有互斥这种能力且消耗资源比互斥量少
该函数std::call_once()调用一次之后就会被标记"已调用"状态
条件变量:std::condition_variable类,生成一个条件变量对象和互斥量配合使用 、
wait()用来等一个东西,如果第二个lambda表达式参数为false,阻塞直到其他某个线程调用notify_one()成员函数为止;如果第二个lambda表达式参数为true,wait()直接返回;如果没有第二个参数和lambda表达式为false一样
notify_one():通知一个线程;其他线程调用此函数将wait(原来睡着/阻塞)的状态唤醒,wait()不断尝试获取互斥量锁,如果获取不到,那么流程就卡在wait()
notify_all():尝试把wait()线程唤醒
欢迎来到奇牛学院学习C++