并发与多线程概述
基于c++11标准
想想轮子哥是怎么说的,反反复复把概念吃透,把知识留存在脑子里
不仅要听懂,练习、变现很重要
1.引言
1.可以给软件开两个线程,分别去处理不同的数据,提高效率。
2.开发多线程,是实力的体现,也是商用的需要。
3.线程开发有难度,理解上更难,需要一定的学习时间,但这恰恰也是实力的体现之处。所以高效突破这个点,可以灵活应用到不同的开发环境和语言环境当中,实现技术壁垒。
4. 放松心情,戒骄戒躁,不要急于求成,要稳住稳打。
2.并发的实现方法
- 多进程并发
- 单进程,多线程并发
3.多进程并发
- 进程之间的通讯。例如账号服务器和游戏逻辑服务器之间的通讯。实现方式包括管道,文件、消息队列,共享内容等(同一个电脑)
- 在不同的电脑上,可以socket通讯
4.多线程并发
- 每个线程都有自己的独立的运行路径,但是一个进程的所有线程共享地址空间(共享内存,比如全局变量在各个线程中都可以使用)
- 全局变量,引用,指针,都可以在线程之间传递。所以使用多线程开销远远小于多进程。
- 共享内存带来的新问题——数据一致性问题:线程A,B,可能在同时向同一个文件写数据,需要有一个先后问题。
- 优先考虑多线程,而不是多进程。(除非一定需要多进程)
- 侧重多线程并发的开发技术,后续谈到并发一般是默认多线程并发。
5.线程的优缺点
- 启动速度快,系统资源开销少,共享内存,轻量级。
- 使用起来有一定难度,数据处理的一致性问题要小心处理
6. c++11新标准线程库
- 以往的创建线程方法,都是操作系统提供一些多线程接口,如windows下包括CreateThread(),_beginthread(), _beginthreadexe()等;在Linux下包括pthread_create()创建线程。临界区,互斥量(不能跨平台编译)
- 线程库一般是跨平台的,windows和Linux系统只需要稍作配置,所以用起来不是很方便。
- 从c++11开始,c++本身增加了多线程执行;即,可以用c++11 这个语言来编写与平台无关的多线程程序,意味着可移植,减少开发人员工作量
…不要懒惰和松懈,多敲… - 若主线程执行完了,子线程会被进程强行终止。如果想保持子线程的运行状态,那么就需要主线程一直保持运行,不要在子线程结束前使主线程运行完毕。(有例外情况)
- 每一个子线程也需要有一个它的入口函数。
7.多线程案例——创建多线程程序
1.函数可调用对象创建线程
#include<thread>
//每个子线程都有自己的初始函数
void myprint(){
cout<<"子线程1"<<endl;
cout<<"子线程2"<<endl;
cout<<"子线程3"<<endl;
cout<<"子线程4"<<endl;
cout<<"子线程5"<<endl;
cout<<"子线程6"<<endl;
cout<<"子线程7"<<endl;
cout<<"子线程8"<<endl;
//...
//...
cout<<"我的线程执行完毕"<<endl;
}
//创建一个thread类对象,也就创建了一条子线程,是和main同时运行的
int main(){
/* thread join */
thread threadObject(pyprint); //thread是一个类,用以创建线程,构造函数的参数是一个入口函数名,也就是一个可调用对象,线程的入口是函数myprint,并且开始了子线程的执行
threadObject.join(); //join函数阻塞主线程,让主线程等待子线程执行完毕后与其汇合,然后主线程再往下走
//上面的thread和join在调试时候的顺序,创建thread -> 监测myprint函数 -> 堵塞主线程,执行join -> 执行子线程的所有内容 -> 堵塞接触,执行主线程 -> 主线程结束.这是传统的多线程任务的写法,即为主线程等待子线程的运行完毕进行收尾
//c++11可以打破传统多线程方法,主线程不必等待,下面介绍
/* detach */
//主线程和子线程分离,不再汇合,且主线程不再等待子线程
//引入detach的原因在于,若令主线程依次等待所有的子线程全部执行完再收尾,不太合适
//但是,最稳定的编程方法,还是传统的封锁机制
//需要注释掉join调用
threadObject.detach(); //使子线程threadObject与主线程失去关联,自己到系统后台运行
//相当于这个子线程被c++运行时库接管
//在该子线程执行完毕后,由运行运行时库负责清理内存空间和相关的资源
//这种进程称为守护线程(脱缰的野马)
//只要主线程结束,子线程后面的内容就不会输出出来
/* joinable */
//用以判断是否可以join或者detach,返回true或者false
if(threadObject.joinable()){cout<<"ok"<<endl;}
else{cout<<"no"<<endl;}
//所以一般来讲,若想进行join封锁主线程,可先调用thread对象的joinable函数来检查是否可以join,
//然后根据判断结果进行join操作,确保安全。
cout<<"hello world1"<<endl;
cout<<"hello world2"<<endl;
cout<<"hello world3"<<endl;
cout<<"hello world4"<<endl;
cout<<"hello world5"<<endl;
cout<<"hello world6"<<endl;
cout<<"hello world7"<<endl;
cout<<"hello world8"<<endl;
return 0;
}
8.其他创建线程的方法
#include<thread>
//thread函数的构造函数中的参数是一个可调用对象,函数是可调用对象,类也是可调用对象
//下面演示用类创建多线程
class TA{
public:
void operator()(){ //必须重载括号,不能待参数
cout<<"我的线程开始执行了"<<endl;
//...
//...
cout<<"我的线程结束执行了"<<endl;
}
};
int main(){
TA ta;
thread Object(ta);
Object.join();//令主线程等待子线程
cout<<"hello world"<<endl;
}
9.创建多个线程
// Thread_1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<thread>
#include<map>
#include<vector>
#include<string>
#include<iostream>
#include<list>
#include<mutex>
using namespace std;
//线程入口函数
void myprint(int inum) {
cout << "myprint线程开始执行,编号=" << inum << endl;
//...
cout << "线程结束" << endl;
return;
}
int main()
{
vector<thread> mythreads;//线程容器
//希望创建10个线程,入口统一都是myprint
for (int i = 0; i < 10; i++) {
mythreads.push_back(thread(myprint, i)); //创建了10个线程,同时这10个线程,已经开始执行
}
for (auto iter = mythreads.begin(); iter != mythreads.end();iter++) {
iter->join(); //等待10个线程都返回,就是说需要依次返回
}
cout << "I love China!" << endl;
return 0;
}