C++并发与多线程学习笔记--基本概念和实现
- 基本概念
- 并发
- 可执行程序、进程、线程
- 学习心得
- 并发的实现方法
- 多进程并发
- 多线程并发
- 总结
- C++标准库
基本概念
(并发、进程、线程)
区分C++初级编程和中高级编程
并发
两个或者更多的任务同时进行:一个程序同时执行多个任务,这些任务之间是相互独立的,以往的计算机只有单核CPU的时候,某个时刻只能执行一个任务。
微观上串行,宏观上并行
1.单核CPU操作系统调度,每秒钟进行任务切换,看起来好像是多任务同时进行,实际上通过切换而来。不同任务之间需要的资源不一样,在切换的时候需要保存一些变量,重新切换回来的时候需要恢复一些变量值(有时间开销、空间开销),任务越多开销越大--上下文切换。
2.多核CPU(多处理器计算机)、服务器和高性能计算领域。或者个人PC多核CPU,真正实现多任务同时进行,任务数少于核数,真正的并行执行。
使用并发的目的:同时完成多个任务,提高性能。
可执行程序、进程和线程
系统中的文件,比如window下的exe文件,或者linxu下的ls la, rwx,有执行权限的文件--可执行的程序。
进程是资源分配的基本单位,一般一个可执行程是一个进程。
1)程序创建之后,就是一个进程。
2)每个进程都有一个主线程,主线程是唯一。
实际上运行程序的时候是进程主线程执行 main函数中的代码,main函数执行完毕,主线程关闭,主线程的生命周期和进程一致。
实际上,执行代码的就是线程==>(操作系统)线程是资源调度的基本单位。“代码的执行道路”。
主线程之外,可以通过自己写代码创建其他线程,其他线程运行的别的程序。
注意:线程并不是越多越好,每个线程都需要一个物理空间,并且进程的上下文切换是有代价,要保存很多中间状态,线程过多,大部分时间都在切换线程上,会耗费本该属于程序运行的时间,如果线程多到一定程度的时候,程序的运行效率不高。
实际环境中,需要多个线程的情形:
玩家1: 充值
玩家2: 抽卡
玩家1用--充值线程-->改数据库,玩家2-->抽卡线程-->改数据库。
线程处理当前的任务之后才进行下一个任务。。。线程给客户端提供服务的过程。
总结
1.线程用来执行代码。
2.线程一条代码的执行通路,一个新线程代表一个新的通路。
3.进程自动包含一个主线程。主线程随着进程启动,通过编码创建其他多个线程:非主线程,但是创建的数量最大不建议超过200~300个,上下文切换耗费时间,实际项目中调优,有时候上下文切换占有太多没有意义的时间。
4.主线程自动启动,一个进程中至少一个线程--主线程。
5.多线程程序--不同线程提供不同的服务,运行效率高,到底有多高难以量化。实际项目中体会和优化。
并发的实现方法
多进程
软件打开后就是一个进程。
账号服务器和游戏逻辑服务器交互:通过端口交换数据。
不同电脑上的socket服务器
多线程
单个进程中,创建了多个线程,每个线程有自己独立的运行路径,但是一个进程中的所有线程共享地址空间(共享内存)。全局变量,指针,引用,都可以在线程之间传递,所以:使用多线程的开销远远小于多进程。
C++标准库
thread
class thread;
Thread
Class to represent individual threads of execution.
A thread of execution is a sequence of instructions that can be executed concurrently with other such sequences in multithreading environments,
while sharing a same address space.An initialized thread object represents an active thread of execution; Such a thread object is joinable, and has a unique thread id.
A default-constructed (non-initialized) thread object is not joinable, and its thread id is common for all non-joinable threads.
A joinable thread becomes not joinable if moved from, or if either join or detach are called on them.
例子:
执行函数:
void my_print() { cout << "my thread start\n"; for (int i = 0; i < 1000; ++i); cout << "my thread end\n" << endl; }
在主函数中方写调用函数的操作
#include <thread> #include <iostream> using namespace std; int main() { //如果要保持自己创建的代码创建的线程,主线程保持运行。有例外 std::cout << "Hello World!\n"; //a)包含一个头文件 //b)创建一个函数 //c)main中开始写代码 thread myThread1(my_print); thread myThread2(my_print); myThread1.join(); myThread2.join(); cout << "Main Thread" << endl; return 0; }
join()
void join();Join threadThe function returns when the thread execution has completed.
This synchronizes the moment this function returns with the completion of all the operations in the thread: This blocks the execution of the thread that calls this function until the function called on construction returns (if it hasn't yet).
After a call to this function, the thread object becomes non-joinable and can be destroyed safely.
join表示阻塞主线程,让主线程等待子线程执行完毕,然后线程再开始往下走。
主线程往下走,当遇到myThread1.join()的时候主线程等待子线程myThread1,当子线程执行完毕,流程汇合,主线程继续往下走。
1)阻塞主线程并等待子线程执行完毕。
2)如果子线程未执行完毕,主线程结束,程序不稳定!!!
detach()
detach(): 主线程不等待子线程。传统多线程创建了很多子线程,让主线程逐个等待子线程结束,这种编程方法不太好,所以引入了detach()。主线程不和子线程汇合,子线程跑到系统后台,相当于被C++运行时刻接管,当这个子线程执行完毕之后,由运行时库负责清理该进程相关的资源。
void detach();Detach threadDetaches the thread represented by the object from the calling thread, allowing them to execute independently from each other.
Both threads continue without blocking nor synchronizing in any way. Note that when either one ends execution, its resources are released.
After a call to this function, the thread object becomes non-joinable and can be destroyed safely.
Linux:驻留后台==>守护线程,不由当前线程执行
std::cout << "Hello World!\n"; //a)包含一个头文件 //b)创建一个函数 //c)main中开始写代码 thread myThread1(my_print,'1'); thread myThread2(my_print,'2'); myThread1.detach(); myThread2.detach(); cout << "Main Thread" << endl;
???放到后台执行了吗?????在主线程最后加一个while(1);可以看到完整的结果
1) detach() 是将函数放到后台执行
2) 主线程退出,子线程也会退出
3) detach() 线程使线程失去我们自己的控制
所以,通常情况下主线程等子线程。一旦detach()之后不能再用join,最常用的是要控制线程的生命周期。
joinable()
判断是否可以成功使用join()或者detach(),复杂的代码的时候,需要用到。
bool joinable() const noexcept;Check if joinableReturns whether the thread object is joinable.
A thread object is joinable if it represents a thread of execution.
A thread object is not joinable in any of these cases:
- if it was default-constructed.
- if it has been moved from (either constructing another thread object, or assigning to it).
- if either of its members join or detach has been called.
其他创建线程的方法
通过类构造一个可调用对象
class MyThread { public: void operator()() //不能带参数 { cout << "my thread start\n"; cout << "my thread end\n" << endl; } };
//类对象的写法 MyThread obj; thread myThread(obj); myThread.join();
如果要传参用如下的写法构造
class MyThread { public: int& m_i; MyThread(int& i) :m_i(i) {} void operator()() { cout << "my thread start\n"; cout << "m_i的值为" << m_i << endl; cout << "my thread end\n" << endl; } };
注意这个例子:如果用detach()后台执行线程obj,主线程和MyThread 的对象obj的运行路径分开,但是参数m_i是主线程按照引用的方式传给obj的,所以会产生如下情况:如果主线程运行结束,会释放变量,那么最后结果中,分离的线程obj会调用一个不存在的变量,运行错误。
主线程执行完之后,构造的对象也会被销毁?为什么使用类没有问题?一旦调用了detach(),那么主线程执行结束了,可调用对象也能执行。因为用了detach()之后把对象复制到线程中,主线程销毁后,复制的对象依然存在,所以没有引用,或者指向这些局部变量的指针,就不会产生问题。===>对象是被复制到线程中去。可以写一个拷贝构造函数,看一下结果。