分类目录: 多线程编程

电子书:C++ Concurrency in Action

C++ Concurrency in Action

在线阅读&下载链接:下载

| 1 分2 分3 分4 分5 分 (4.50- 8票) Loading ... Loading ... | 同时归档在:C/C++, 语言基础 | 标签: |

一种常见的并发编程场景的处理

对于并发编程,大家想到总是多线程之间对等的临界资源竞争。然而经常会遇到下面这样的场景:

守护线程提供一个临界资源,多个子线程会并发改写该临界资源。大部分时候(99.9%的时间),主线程是不会干涉各个线程之间的竞争的,通常只要该临界资源自己内部处理好同步即可。但是偶尔主线程也会干预一下该临界资源,比如做一些统计,做一个快照,或者复制数据然后清空等。这个操作通常会耗时比较长,并且在此期间不希望有人改写临界资源。如果,主线程与各个子线程使用同样的锁或者synchronized同步,那么在主线程没有作该操作时,各个子线程之间会因为竞争而阻塞,这个阻塞开起来是没有必要的。

这里介绍一个利用volatile变量的特性解决该问题的方案。尝试在性能和数据保护上面达到最大平衡。Atomic变量是采用的寄存器(volatile)变量实现的。用两个变量标志map表当前的状态。而不必在后台线程和众多业务线程之间加锁或者同步,由于在多数情况下volatile变量的性能优于锁(Java 理论与实践: 正确使用 Volatile 变量)。这里只以map表举例,其他保证线程安全的数据结构也适用。

阅读全文 »

| 1 分2 分3 分4 分5 分 (5.00- 4票) Loading ... Loading ... | 同时归档在:Java, 语言基础 | 标签: , |

关于Java中Random的一些使用细节

Random类相信大家都不陌生,但是必须掌握一些特定的细节才能在要求较高的场合用好该变量。这里分析一个多线程环境下Random的使用。

现在面临一个问题:有多个线程需要按照随机的方式取一个令牌,尽量让每个线程取得的令牌不一样,可以认为令牌就是一个数字,如1~100之内的一个整数。那么怎样实现能最好的解决这个问题呢?首先想到的是用一个同步的变量,使用一个getAndIncrement接口,这样每个线程调用这个接口时都能获得一个与其他同时访问该接口不一样的值。当然这不是本帖想要讨论的问题。本帖希望用随机数生成器的方式给每一个线程提供这个接口。

于是一位粗心的同学有了下面这样的接口:

阅读全文 »

| 1 分2 分3 分4 分5 分 (4.86- 7票) Loading ... Loading ... | 同时归档在:Java, 语言基础 | 标签: , , |

JVM并发机制的探讨——内存模型、内存可见性和指令重排序

转自oschina

并发本来就是个有意思的问题,尤其是现在又流行这么一句话:“高帅富加机器,穷矮搓搞优化”。从这句话可以看到,无论是高帅富还是穷矮搓都需要深入理解并发编程,高帅富加多了机器,需要协调多台机器或者多个CPU对共享资源的访问,因此需要了解并发,穷矮搓搞优化需要编写各种多线程的代码来压榨CPU的计算资源,让它在同一时刻做更多的事情,这个更需要了解并发。

在我前一篇关于并发的文章http://my.oschina.net/chihz/blog/54731中提到过管程,管程的特色是在编程语言中对并发的细节进行封装,使程序员可以直接在语言中就得到并发的支持,而不必自己去处理一些像是控制信号量之类容易出错且繁琐的细节问题。一些语言是通过在编译时解开语法糖的方式去实现管程,但Java在编译后生成的字节码层面上对并发仍然是一层封装,比如syncrhonized块在编译之后只是对应了两条指令:monitorenter和monitorexit。更多的并发细节是在JVM运行时去处理的,而不是编译。这篇文章主要是针对JVM处理并发的一些细节的探讨。

JAVA内存模型

阅读全文 »

| 1 分2 分3 分4 分5 分 (5.00- 3票) Loading ... Loading ... | 同时归档在:Java | 标签: |

[转] 深入多线程编程

线程库
多线程编程定式
无锁编程(Lock Free)
阻塞型同步(Blocking Synchronization)
非阻塞型同步(Non-blocking Synchronization)
优先级反转(Priority Inversion)
优先级继承(Priority Inheritance)
优先级顶置(Priority Overhead)
内存屏障

转载自:http://blog.chinaunix.net/uid-20682147-id-3160080.html

PDF文档查阅链接:

深入多线程编程

| 1 分2 分3 分4 分5 分 (5.00- 1票) Loading ... Loading ... | 归档目录:多线程编程 | 标签: , , |

[转] 高并发系统设计

一、服务器内部设计

服务器设计涉及Socket的阻塞/非阻塞,操作系统IO的同步和异步(之前被人问到过两次。第一次让我说说知道的网络模型,我说ISO模型和TCP/IP模型,结果被鄙视了。最后人说了解linux epoll吗?不了解呀!汉,回去查资料才知道是这回事。第二次让我说说知道线程模型,汉!这个名词感觉没有听说过,线程?模型?半同步/半异步,领导者/跟随者知道吗。再汉,我知道同步/异步,还有半同步/半异步?啥呀?领导者/跟随者,我现在没有领导。回去一顿恶补,原来是ACE框架里边经常有这样的提法,Reactor属于同步/半同步,PREACTOR属于领导者/跟随者模式。瀑布汗。小插曲一段,这些不懂没关系,下边我慢慢分解),事件分离器,线程池等。内部设计希望通过各个模块的给出一个简单设计,经过您的进一步的组合和打磨,就可以实现一个基本的高并发服务器。

1. Java高并发服务器

Java设计高并发服务器相对比较简单。直接是用ServerSocket或者Channel+selector实现。前者属于同步IO设计,后者采用了模拟的异步IO。为什么说模拟的异步IO呢?记得网上看到一篇文章分析了java的selector。在windows上通过建立一个127.0.0.1到127.0.0.1的连接实现IO的异步通知。在linux上通过建立一个管道实现IO的异步通知。考虑到高并并发系统的要求和java上边的异步IO的限制(通常操作系统同时打开的文件数是有限制的)和效率问题,java的高并发服务器设计不做展开深入的分析,可以参考C高并发服务器的分析做同样的设计。

2. C高并发服务器设计
1) 基本概念

Ø 阻塞和非阻塞socket

所谓阻塞Socket,是指其完成指定的任务之前不允许程序调用另一个函数,在Windows下还会阻塞本线程消息的发送。所谓非阻塞Socket,是指操作启动之后,如果可以立即得到结果就返回结果,否则返回表示结果需要等待的错误信息,不等待任务完成函数就返回。一个比较有意思的问题是accept的Socket是阻塞的还是非阻塞的呢?下边是MSDN上边的一段话:The accept function extracts thefirst connection on the queue of pending connections on socket s. It thencreates and returns a handle to the new socket. The newly created socket is thesocket that will handle the actual connection; it has the same properties assocket s, including the asynchronous events registered with the WSAAsyncSelector WSAEventSelect functions.

Ø 同步/异步IO

有两种类型的文件IO同步:同步文件IO和异步文件IO。异步文件IO也就是重叠IO。 
      在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。 
      如果IO请求需要大量时间执行的话,异步文件IO方式可以显著提高效率,因为在线程等待的这段时间内,CPU将会调度其他线程进行执行,如果没有其他线程需要执行的话,这段时间将会浪费掉(可能会调度操作系统的零页线程)。如果IO请求操作很快,用异步IO方式反而还低效,还不如用同步IO方式。 
      同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读写操作。重叠IO允许一个或多个线程同时发出IO请求。异步IO在请求完成时,通过将文件句柄设为有信号状态来通知应用程序,或者应用程序通过GetOverlappedResult察看IO请求是否完成,也可以通过一个事件对象来通知应用程序。高并发系统通常采用异步IO方式提高系统性能。

Ø 事件分离器

事件分离器的概念是针对异步IO来说的。在同步IO的情况下,执行操作等待返回结果,不要事件分离器。异步IO的时候,发送请求后,结果是通过事件通知的。这是产生了事件分离器的需求。事件分离器主要任务是管理和分离不同文件描述符上的所发生的事件,让后通知相应的事件,派发相应的动作。

Ø 线程池

线程池基本上比较简单,实现线程的借入和借出,创建和销毁。最完好可以做到通过一个事件触发一个线程开始工作(注:在epoll中,事件触发又分为边沿触发和水平触发)。

2) 常见的设计模式

根据Socket的阻塞非阻塞,IO的同步和异步。可以分为如下4中情形

阻塞同步 阻塞异步
非阻塞同步 非阻塞异步

阻塞同步方式是原始的方式,也是许多教科书上介绍的方式,因为Socket和IO默认的为阻塞和同步方式。基本流程如下:

listen_fd = socket( AF_INET,SOCK_STREAM,0 )

bind( listen_fd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr_in))

listen( listen_fd,1 )

accept( listen_fd, (struct sockaddr*)&remote_addr,&addr_len )

recv( accept_fd ,&in_buf ,1024 ,0 )

close(accept_fd)

阻塞异步方式有所改进,但是Socket的阻塞方式,前一个连接没有处理完成,下一个连接不能接入,是高并发服务器所不可接收的方式。只不过在上边阻塞同步方式的基础上使用select(严格来说select是一种IO多路服用技术。因为linux尚没有完整的实现异步IO,而winsock实在理解socket没有linux上面那么直观。,这里为了方便,没有做严格的区分)或者其它异步IO方式。

非阻塞同步方式,通过设置socket选项为NONBLOCK,可以很快的接收连接,但是处理采用同步IO方式,服务器处理性能也比较差。

上边三种方式不做深入介绍。下边主要从非阻塞异步IO方式介绍。

非阻塞异步IO方式中,由于异步IO方式在同一系统可能有多种实现,不同系统也有不同实现,下边介绍几种常见的IO方式和服务器框架。

Ø Select

Select采用轮训注册的fd方式。是一种比较老的IO多路服用实现方式,效率相对要差一些。Select方式在windows和linux上都支持。

基本框架如下:


socket( AF_INET,SOCK_STREAM,0 )
fcntl(listen_fd, F_SETFL,flags|O_NONBLOCK);
bind( listen_fd, (structsockaddr *)&my_addr,sizeof(struct sockaddr_in))
listen( listen_fd,1 )
FD_ZERO( &fd_sets );
FD_SET(listen_fd,&fd_sets);
for(k=0; k<=i; k++){

FD_SET(accept_fds[k],&fd_sets);
}
events = select( max_fd + 1,&fd_sets, NULL, NULL, NULL );
if(FD_ISSET(listen_fd,&fd_sets) ){
accept_fd = accept( listen_fd, (structsockaddr
*)&remote_addr,&addr_len );
}
for( j=0; j<=i; j++ ){
if(
FD_ISSET(accept_fds[j],&fd_sets) ){

recv( accept_fds[j] ,&in_buf ,1024 ,0 );
}
}

Ø Epoll

Epoll是linux2.6内核以后支持的一种高性能的IO多路服用技术。服务器框架如下:

socket( AF_INET,SOCK_STREAM,0 )
fcntl(listen_fd, F_SETFL,flags|O_NONBLOCK);
bind( listen_fd, (structsockaddr *)&my_addr,sizeof(struct sockaddr_in))
listen( listen_fd,1 )
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev);
ev_s = epoll_wait(epfd,events,20,500 );
for(i=0; i<ev_s;i++){

if(events[i].data.fd==listen_fd){

accept_fd = accept( listen_fd,(structsockaddr *)&remote_addr,&addr_len
);

fcntl(accept_fd, F_SETFL,flags|O_NONBLOCK);

epoll_ctl(epfd,EPOLL_CTL_ADD,accept_fd,&ev);

}

else if(events[i].events&EPOLLIN){

recv( events[i].data.fd ,&in_buf,1024 ,0 );

}
}

Ø AIO

在windows上微软实现了异步IO,通过AIO可以方便的实现高并发的服务器。框架如下:

WSAStartup( 0x0202 , & wsaData)
CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0 , 0 )
WSASocket(AF_INET,SOCK_STREAM, 0 , NULL, 0 , WSA_FLAG_OVERLAPPED)
bind(Listen, (PSOCKADDR) & InternetAddr, sizeof
(InternetAddr))
listen(Listen, 5 )
WSAAccept(Listen, NULL, NULL,NULL, 0 )
PerHandleData =(LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof
(PER_HANDLE_DATA)
CreateIoCompletionPort((HANDLE)Accept, CompletionPort, (DWORD) PerHandleData,
0 )
PerIoData= (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof
(PER_IO_OPERATION_DATA))
WSARecv(Accept,&(PerIoData->DataBuf),1,&RecvBytes,&Flags,&(PerIoData->Overlapped),
NULL)
(GetQueuedCompletionStatus(CompletionPort, & BytesTransferred,
(LPDWORD) &
PerHandleData,(LPOVERLAPPED * ) & PerIoData, INFINITE)
if (PerIoData -> BytesRECV >PerIoData ->
BytesSEND){
WSASend(PerHandleData-> Socket, & (PerIoData
->DataBuf), 1 , & SendBytes, 0 ,

& (PerIoData ->Overlapped), NULL)
}

3) 引入线程池和事件分离器后

由于上边只是单纯的使用非阻塞Socket和异步IO的方式。提高了接收连接和处理的速度。但是还是不能解决两个客户端同时连接的问题。这时就需要引入多线程机制。引入多线程后,又有许多策略。Linux上通常采用主进程负责接收连接,之后fork子进程处理连接。Windows通常采用线程池方式,避免线程创建和销毁的开销,当然linux上也可以采用线程池方式。采用多进程和多线程方式后。事件处理也可以再优化,定义一个简单的事件处理器,把所有事件放入一个队列,各个线程去事件队列取相应的事件,然后自己开始工作。这就是我上边提到的半同步/半异步方式了。如果线程工作的时候是接收到连接后,自己处理后续的发送和接收,然后选出另外一个线程作为领导继续接收连接,其它线程作为追随者。这就是领导者/追随者模式了。具体可以参考ACE的Reactor和Preactor的具体实现。半同步和/半异步网上也有很多的讨论,可以自己深入研究。代码就比较复杂了,这里就不给出代码了。

二、分布式系统设计

前面讲述了分布式系统中的核心的服务器的实现。可以是http服务器,缓存服务器,分布式文件系统等的内部实现。下边主要从一个高并发的大型网站出发,看一个高并发系统的设计。下边是一个高并发系统的逻辑结构:

分布式系统设计

主要是参考这篇文章http://www.chinaz.com/web/2010/0310/108211.shtml。下边主要想从这个架构的各个部分的实现展开。

1. 缓存系统

缓存是每一个高并发,高可用系统不可或缺的模块。下边就几个常见缓存系统系统进行介绍。

Squid

Squid作为一个前端缓存,通常部署在网络的离用户最近的地方,通过缓存网站的页面,使用户不必每次都跑到服务器去取数据,提高系统响应和性能。实现应该比较简单:一个带有存储功能的代理。用户访问页面的时候,由它代理,然后存储请求结果,下次再访问的时候,查看是否需要更新,有更新就去服务器取新数据,否则直接返回用户页面。

Ehcache

Ehcache是一个对象缓存系统。通常在J2EE中配合Hibernate使用,这里请原谅作者本人之前是做J2EE开发的,其它使用方式暂不是很了解。应用查询数据库,对经常需要查询,却更新不频繁的数据,可以放入ehcache缓存,提高访问速度。Ehcahe支持内存缓存和硬盘两种方式,支持分布式缓存。数据缓存的基本原理就是:为需要缓存的对象建立一个map,临时对象放入map,查询的时候先查询map,没有找到再查找数据库。关机时可以序列化到硬盘。分布式缓存没有研究过。

页面缓存和动态页面静态化

在大型网站经常使用的一种缓存技术就是动态页面的缓存。由于动态页面经常更新,上边的缓存就不起作用了。通常会采用SSI(Server side include)等技术将动态页面的或者页面片段进行缓存。

还有一种就是动态页面静态化。

2. 负载均衡系统

Ø 负载均衡策略

负载均衡策略有随机分配,平均分配,分布式一致性hash等。随机分配就是通过随机数选择一个服务器来服务。平均分配就是一次循环分配一次。分布式一致性hash算法,比较负载,把资源和节点映射到一个换上,然后通过一定的算法资源对应到节点上,使得添加和去掉服务器变得非常容易,减少对其它服务器的影响。很有名的一个算法,据说是P2P的基础。了解不是很深,就不详细说了,要露马脚了。

Ø 软件负载均衡

软件负载均衡可以采用很多方案,常见的几个方案有:

基于DNS的负载均衡,通过DNS正向区域的配置,将一个域名根据一定的策略解析到多个ip地址,实现负载均衡,这里需要DNS服务器的配合。
基于LVS的负载均衡。LVS可以将多个linux服务器做成一个虚拟的服务器,对外提供服务器,实现负载均衡。
基于Iptables的负载均衡。Iptables可以通过做nat,对外提供一个虚拟IP,对内映射到多个服务器实现负载均衡。基本上可以和硬件均衡方案一致了,这里的linux服务器相当于一台路由器。

Ø 硬件负载均衡

基于路由器的负载均衡,在路由器上配置nat实现负载均衡。对外网一个虚拟IP,内网映射几个内网IP。一些网络设备厂商也提供了一些负载均衡的设备,如F5,不过价格不菲哦。数据库的负载均衡数据库的负载均衡可以是数据库厂商提供的集群方案。

云计算

转载信息:

作者:周顺利
原文链接:http://blog.csdn.net/shatty/article/details/6629896
注:本文大多数观点和代码都是从网上或者开源代码中抄来的,为了疏理和组织这片文章,作者也费了不少心血,为了表示对我劳动的尊重,请转载时注明作者和出处。

| 1 分2 分3 分4 分5 分 (4.50- 6票) Loading ... Loading ... | 同时归档在:C/C++, IO编程 | 标签: , , , , , |
返回顶部