http://www.zyfforlinux.cc/2015/05/20/linux%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E7%BC%96%E7%A8%8B-%E7%AC%AC%E4%B8%89%E7%AB%A0/
http://www.cnblogs.com/my_life/articles/5315034.html
3.1进程和线程
- 每个进程有自己的独立的地址空间
- 线程的特点就是共享地址空间,从而高效的共享数据
- 如果多个进程大量共享内存,等于是把多进程程序当做多线程来写了。
- 多线程的价值在于更好的发挥了多核处理器的效能,在单核时代,多线程没有多大价值。
3.2单线程服务器的常用编程模型
在高性能的网络程序中,使用最为广泛的恐怕要数non-blocking IO + IO multiplexing这种模型了也就是Reactor模式
Reactor模式执行的步骤:
- 等待事件 (Reactor 的工作)
- 发”已经可读”事件发给事先注册的事件处理者或者回调 ( Reactor 要做的)
- 读数据 (用户代码要做的)
- 处理数据 (用户代码要做的)
Reactor模式的基本编程模型:
while(!done)
{
int timeout_ms = max(1000,getNextTimedCallback());
int retval = poll(fds,nfds,timeout_ms);
if (retval < 0) {
//处理错误,回调用户的error handler
} else {
//处理到期的timers,回调用户的handler
//处理IO时间,回调用户的IO event handler
}
}
Reactor模型的优点很明显,编程不难,效率还不错。
3.3多线程服务器的常用编程模型
- 每个请求创建一个线程,使用阻塞式IO
- 使用线程池,同样使用阻塞式IO
- 使用non-blocking IO + IO multiplexing
- 时候用Leader/Follower等高级模式
通常选择non-blocking IO + IO multiplexing
3.3.1one loop per thread (这也是作者自创的一个术语,可忽略。因为对于所有的IO线程,总得有个主线程负责调度,或者各个IO线程需要轮询或者抢占地来处理IO事件)
此种模型下程序里的每个IO线程有一个Event loop,用于处理读写和定时事件。也就是每一个线程都是一个Reactor
这种模式的好处在于:
- 线程数目基本固定,可以在程序启动的时候设置,不会频繁的创建和销毁
- 可以很方便地在线程间调配负载
- IO事件发生的线程是固定的,同一个TCP连接不必考虑事件并发
Event loop代表了线程的主循环(主线程的循环),需要让哪个线程干活,就把timer或IO channel注册到哪个线程的loop里即可。
多线程程序要求对event loop必须是线程安全的。
3.3.2线程池
对于那种没有IO,而光有计算任务的线程,使用event loop有点浪费,
可以使用blocking queue实现任务队列
typedef boost::function<void()> Functor;
BlockingQueue<Functor> taskQueue;
void workerThread()
{
while(running) //running变量是个全局标志
{
Functor task = taskQueue.take(); //这是阻塞的
task();
}
}
创建了数量为N的线程池:
int N = num_of_computinhg_threads;
for(int i = 0;i < N;i++)
{
create_thread(&workerThread); //伪代码,启动线程
}
简单使用:
Foo foo; //Foo有calc()成员函数,实际线程要执行的函数。
boost::function<void()> task = boost::bind(&Foo::calc,&foo); //函数进行了绑定
taskQueue.post(task); //放入要执行的函数
BlockingQueue是多线程编程的利器,利用BlckingQueue可以实现数据的生产者消费者队列。
3.3.3推荐模式
总结起来: 推荐C++多线程服务器端编程模式为: one (event)loop per thread(用于事件处理:每个线程都是一个reactor,都有其自己的事件循环epoll之类) + thread pool(用于计算)
- event loop(也叫IO loop)用作IO multiplexing,配合non-blocking IO和定时器
- thread pool用来做计算,具体可以是任务队列或生产者消费者队列
3.4进程间通信只用TCP
常见的IPC通信方式:
匿名管道,具名管道,POSIX消息队列,共享内存,信号,socket
常见的同步原语:
互斥锁,条件变量,读写锁,文件锁,信号量等
进程间通信首选Sockets,其最大的好处在于,可以跨主机,具有伸缩性。
在编程上,TCP Sockets和pipe都是操作文件描述符的,用来收发字节流,都可以read/write
/fcntl/select/poll等,不同的是TCP是双向的,pipe是单向的,而且还要求进程之间是父子关系
通过socketpair可以创建一个双向收发的pipe,sockets更通用。
使用TCP协议的一个天生的好处在于可记录和可重现,tcpdump Wireshark是解决两个进程间协议
和状态争端的好帮手,也是性能分析的利器。
分布式系统中使用TCP长连接通信
一个分布式系统就是运行在多台机器上的多个进程组成的,进程之间采用TCP长连接通信。
有两点好处:
1.容易定位分布式系统中的服务之间的依赖关系
通过使用netstat -tpna|grep :port 就能列出用到某服务的客户端地址。
2.通过接收和发送的长度比较容易定位网络或程序故障
Recv-Q保持不变或持续增加意味着服务进程的处理速度变慢了,可能发生了死锁或阻塞。
Send-Q保持不变或持续增加,有可能是对方服务器太忙,来不及处理。也有可能是网络中间
某个路由或交换机故障造成的丢包等等,这些因素都可能表现为数据发送不出去。
3.5多线程服务器的适用场合
一些常用的多线程模式:
- 1.运行一个单线程的进程(就是单进程)
- 2.运行一个多线程的进程
- 3.运行多个单线程的进程(多进程模式)
- 4.运行多个多线程的进程
模式1 不可伸缩,不能发挥多核处理机器的计算能力。
模式3 是目前公认的主流模式,它有以下两个子模式:
3a 简单的把模式1中的进程运行多份
3b 主进程+worker进程
模式2 是被很多人所唾弃的,认为多线程程序难写
模式4 不但没有结合2和3的优点,反而汇聚了二者的缺点
无论是IO bound还是CPU bound的服务,多线程都没有什么大优势。
在业务处理逻辑所花费的开销小于创建进程的开销的时候,可以选择使用线程来处理
多线程不是万灵丹,它有其适用的场合。
3.5.1 必须使用单线程的场合
两种必须使用单线程的场合:
1.程序可能会fork
2.限制程序的CPU占用率
单线程能避免过分抢夺系统的计算资源。
3.5.2单线程程序的优缺点
优点在于,程序结构简单明了。缺点就是它是非抢占的同时处理多个事件的时候容易发生优先级反转。
多线程程序在性能上并没有啥优势。
3.5.3适用多线程程序的场景
提高响应速度,让IO和计算相互重叠,降低latency,多线程不能提高绝对性能,但是可以提高平均响应性能。
线程的分类:
- IO线程,这类线程主循环是IO multiplexing,阻塞地等在select/poll/epoll_wait系统调用上。
- 计算线程,这类线程的主循环是blocking queue,阻塞等待在condition variable上。这类线程一般位于线程池中
- 第三库使用的线程,比如logging,又比如说database connection
3.6多线程服务器的适用场合
- linux能同时启动多个线程?
线程的默认栈大小是10MB,对于32位系统一个进程最多大约同时启动300个线程。对于64位系统,线程数目可大
大增加。
- 多线程能提高并发度吗?
如果指的是”并发连接数”则不能,使用thread per connection的模型那么并发连接数最多是300。但是使用
one loop per thread 则单个event loop处理1万并发长连接并不罕见。
thread per connection不适合高并发场合,其scalability不佳,one loop per thread的并发度足够大。且和
CPU数目成正比。
- 多线程可以提高吞吐量吗?
对于计算密集型服务,不能提高吞吐量。(没有理解),但是一般计算型任务是可以提高吞吐量的。
-
多线程能降低响应时间吗?
可以 -
多线程程序如何让IO和”计算”相互重叠,降低latency?
基本思路就是把IO操作,通过BlockingQueue交给别的线程取做,自己不必等待。 -
为什么第三方库往往要用自己的线程?
event loop模型没有标准实现,如果自己写代码,尽可以按照Reactor的推荐方式来编程,但第三方库不一定
能很好地适应并融入这个event loop framework,有时需要用线程来做一些串并转换。所以第三方库要想融入
event loop可以其一个线程来查询第三方库的事件,然后通过pipe转换为文件描述符的读写事件。 -
什么是线程池大小的阻抗匹配原则?
密集计算所占的时间比重为P(0 < P <= 1),而系统一共有C个CPU,为了让这个C个CPU跑满而又不过载,线程池
的大小的经验公式为T=C/P 考虑到P值的估计不是很准确,T的最佳值可以上下浮动50%。如果P小于0.2这个公式
不再适用。 -
除了你推荐的Reactor+thread poll,还有别的non-trivial多线程编程模型吗?
Proactor模式 -
一个多线程的进程和多个单进程进程的取舍?
根据程序一次请求访问内存大小进行取舍,如果访问内存大就用多线程,避免CPU cache换入换出。影响性能,
否则就用单线程多进程,享受单线程编程的便利。
线程不能减少工作量,即不能减少CPU时间,如果解决一个问题,需要执行一亿条指令没那么用多线程只会让这个
数字增加,但是通过合理的分配这个一亿条指令在多个核上,那么能让工期提前结束。
浙公网安备 33010602011771号