目录
一:前言
在学习完Client/Server架构后,需要开发服务器以及客户端,对于客户端之间数据交互问题,需要解决
若是一个进程表示一个客户端,多个客户端上线可fork创建多个进程,可是对于多个客户端若是数据无法交互(进程间数据不共享),就无法实现聊天系统业务功能(进程间数据共享虽然可以使用IPC技术来实现,但是实现的过程也是非常麻烦的,因此需要学习更好的技术来解决数据交互的问题)
对于上述数据交互这一问题,需要学习多线程技术,可以试想,若是一个进程中开多个线程,那么这多个线程是共享同一个进程中的资源,也就是说明了这多个线程之间的数据就是共享的,可以实现数据交互,因为它们本就同属于一个进程)
此次文章 主要介绍的就是多线程技术,目前是要实现,每有一个客户端上线,服务器就会为其创建线程,若要实现多个客户端之间数据交互,可创建多条线程,多线程之间数据共享,这就是最佳实践
二:进程 & 线程
在一个程序里(进程)的多个执行路线就叫做线程(thread)
更准确的定义是:线程是“一个进程内部的控制序列”
线程是包含在进程内部的,计算机识别运行的都是线程,我们看到的任务管理器是进程
进程是资源分配的基本单位,线程是cpu调度的基本单位
表面上我们看到的是进程,但一个进程至少有一个执行线程(进程包含线程),
实际上也就是说,从计算机底层上来看是线程被CPU给调用了
一个进程中可以开无限多个线程,当然前提是要在内存硬件方面允许的前提条件下,现实中大多数计算机在测试时,差不多也就开三百个左右的线程就创建不下去了
进程只是一个分配的单位,怎么计算内存空间怎么计算耗费资源
一个进程内部可以包含多条线程,线程之间可以资源共享
为什么线程能够共享资源 :
因为线程是包含在一个进程里,而这个进程分配资源,
这个进程里包含多条线程,这些线程是在共享这个进程中的所有资源
三:fork创建进程 & 进程创建线程
当一个进程执行一个fork调用的时候,会创建出进程的一个新拷贝,新进程将拥有它自己的变量和它自己的PID
这个新进程的运行时间是独立的,它在执行时几乎完全独立于创建它的进程
在进程里面创建一个新线程的时候,新的执行线程会拥有自己的堆栈(因此也就有自己的局部变量),但要与它的创建者(进程)共享全局变量、文件描述符、信号处理器和当前的子目录状态。
四:线程优缺点
优点:
创建一个新线程的代价要比创建一个新进程小得多【进程包含线程】
与进程之间的切换相比,线程之间的切换需要操作系统做的工作至少在理论上要少很多
线程占用的资源要比进程少很多
进程间共享数据需要IPC ,线程之间共享全局变量(线程在同一个进程中)
缺点:
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的
调试一个多线程程序也比调试一个单线程程序困难得多
五:CPU时间片轮转
核心数量:十二核心
线程数:二十线程
对于上述配置,在一个核心单位时间可以同时跑二十个线程 ,CPU满载情况下,同一时间单位时间1ms可以跑12*20=240个线程
如果是很久以前的电脑单核心单线程,1*1=1个线程
但其实很久以前的电脑开软件开三四个也是没有问题的,现在的电脑配置确实很高,本人喜欢打游戏,也喜欢配置高的电脑,但是在电脑配置购物时,还是要讲究性价比呀
核心数量:十核心
线程数:十六线程
对于上述配置。CPU满载,10*16=160线程;相比上面240少了80
那如果假设是一个进程中包含两个线程 ,就少跑了40个进程
由此可见,计算机根据不同硬件性能列出不同的价位,毕竟一分钱一份货嘛
CPU时间轮片
1s=1000ms ,以前的电脑单核心单线程,1*1=1个线程,以单位1ms来看,都有1000个线程
同时开多个软件,只是使用了一个软件,而不使用其他的软件,正在使用的是前台,没有在使用的叫后台
这个时候CPU时间轮片就会多关照前台(用户正在使用的软件)
1s = 1000ms 100个线程,按照平均来看,会轮转10次,但实际不是
假设一个前台程序会用900ms都在跑一个线程
那剩余99个线程(后台软件)只用100ms就解决了
由此可见,CPU是非常灵活的,轮片是按需分配的
小结:
每个线程的执行时间随机 同时 每个线程的执行顺序也是随机的
CPU时间轮片 无法通过代码改变
能做到的只有线程的创建,线程的暂停(停止),但线程什么时候被CPU调用不知道
CPU只能保证 是一个线程 就一定会跑 但什么时候跑 跑什么时候CPU说了算(从线程来看 是需要CPU执行权的争抢 从CPU来看 每一个都一样 每一个都至少跑一次)
1核心 4线程 和 2核心 2线程 表面上是一样的,但是实际上最好选单核心,买电脑不能只注重核心数,要综合考虑线程的,单位核心线程越多的电脑越好
苹果电脑软件和硬件都是自己生产的,生态比较好,算法(内存 CPU调用)的结构比较好(就比如安卓手机经常需要清理后台,而苹果不用,因为只有在运行软件的时候才会占用内存,不运行软件的时候整个内存都会释放出来,因此,你在使用苹果手机,都是一个软件一个软件地用,基本不会出现有卡顿的现象,本人用的苹果产品比较多,对苹果产品还是比较认可的,当然也不是让大家消费哈,无论购买什么产品都是看自身需求的!
六:线程函数库
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些函数库,要通过引入头文件<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项
pthread_create函数
作用:创建一个新的线程
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void*(*start_routine)(void*),void *arg);
thread:新线程创建成功后,保存新线程的标识符
attr:设置线程的属性,一般不需要什么特殊的属性,直接传 NULL即可
start_routine: 是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
可以查看pthread_create函数使用说明
七:多线程技术案例
引用了头文件,还是出现未定义引用
原因:VS默认情况下不支持多线程
解决: 右键工程属性
链接器 命令行下 输入-pthread 点击应用 点击确定 即可
线程测试一:
正常运行 在接收程序时候,进程结束,主子线程也都会结束(因为进程包含线程)
每个线程的执行时间随机 同时 每个线程的执行顺序也是随机的
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_function(void* pv)
{
while (1)
{
cout << "子进程.............run" << endl;
sleep(1);
}
}
int main()
{
pthread_t threadid;
pthread_create(&threadid, NULL, pthread_function, NULL);
while (1)
{
cout << "主进程的 主线程...............run" << endl;
sleep(1);
}
return 0;
}
若是虚拟机设置如下,就可以8条线程同时走
就会有类似下图的运行结果
若是ubuntu下的手动编译,
编译命令需要写上-pthread,否则会编译不过去,需要注意!
线程测试二
同一进程,不同线程之间,数据共享(线程间可以将全局变量数据进行共享)
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
int number = 0;
void* pthread_function1(void* pv)
{
while (1)
{
number++;
cout << "子进程1.............run --- number = " << number << endl;
sleep(1);
}
}
void* pthread_function2(void* pv)
{
while (1)
{
number++;
cout << "子进程2.............run --- number = " << number << endl;
sleep(1);
}
}
int main()
{
pthread_t threadid;
pthread_create(&threadid, NULL, pthread_function1, NULL);
pthread_create(&threadid, NULL, pthread_function2, NULL);
while (1)
{
cout << "主进程的 主线程...............run" << endl;
sleep(1);
}
return 0;
}
线程测试三
新的执行线程会拥有自己的堆栈(因此也就有自己的局部变量)(局部变量线程之间数据不共享)
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_function1(void* pv)
{
int x = *(int*)pv;
while (1)
{
x++;
cout << "子进程1.............run --- number = " << x << endl;
sleep(1);
}
}
void* pthread_function2(void* pv)
{
int y = *(int*)pv;
while (1)
{
y++;
cout << "子进程2.............run --- number = " << y << endl;
sleep(1);
}
}
int main()
{
int number = 0;
pthread_t threadid;
pthread_create(&threadid, NULL, pthread_function1, &number);
pthread_create(&threadid, NULL, pthread_function2, &number);
while (1)
{
cout << "主进程的 主线程...............run" << endl;
sleep(1);
}
return 0;
}
线程测试四
以传参的形式 将原地址上数据++进行测试 依旧可以实现线程之间数据共享
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_function1(void* pv)
{
while (1)
{
(*(int*)pv)++;
cout << "子进程1.............run --- number = " << (*(int*)pv) << endl;
sleep(1);
}
}
void* pthread_function2(void* pv)
{
while (1)
{
(*(int*)pv)++;
cout << "子进程2.............run --- number = " << (*(int*)pv) << endl;
sleep(1);
}
}
int main()
{
int number = 0;
pthread_t threadid;
pthread_create(&threadid, NULL, pthread_function1, &number);
pthread_create(&threadid, NULL, pthread_function2, &number);
while (1)
{
cout << "主进程的 主线程...............run" << endl;
sleep(1);
}
return 0;
}