在本文中,我们将讨论为建立高度并发的应用程序而逐渐建立的一些设计原理和模式。
但是,值得注意的是,设计并发应用程序是一个广泛而复杂的主题,因此,没有任何教程可以声称对它的处理是详尽无遗的。我们这里将介绍一些常用的技巧!
在继续进行之前,先来了解基础知识。首先,我们必须阐明我们对所谓的并发程序的理解:如果同时进行多个计算,则我们将程序称为并发程序。
现在,请注意,我们已经提到了计算是同时发生的,也就是说,它们是同时进行的。但是,它们可能同时执行或不同时执行,了解差异是很重要的,因为同时执行的计算称为parallel。
了解我们如何创建并发模块很重要。有很多选项,但我们将在此处重点介绍两个流行的选择:
如果并发模块不必通信,这是非常理想的,但是通常不是这种情况。这产生了两种并发编程模型:
自从摩尔定律在处理器的时钟速度方面碰壁以来已经有一段时间了。取而代之的是,由于必须增长,因此我们开始将多个处理器打包到同一芯片上,通常称为多核处理器。但是,听到具有32个以上内核的处理器并不罕见。
现在,我们知道单个内核一次只能执行一个线程或一组指令。但是,进程和线程的数量可以分别为数百和数千。那么,它如何真正起作用?这是操作系统为我们模拟并发的地方。操作系统通过时间分片来实现这一点-这实际上意味着处理器会频繁地,不可预测地且不确定地在线程之间切换。
在很大程度上,我们在并行编程方面的经验涉及将本机线程与共享内存一起使用。因此,我们将专注于由此产生的一些常见问题:
现在,我们了解了并发编程的基础知识以及其中的常见问题,是时候了解一些避免这些问题的常见模式了。我们必须重申,并发编程是一项艰巨的任务,需要大量经验。因此,遵循某些已建立的模式可以使任务更容易。
我们将针对并发编程讨论的第一个设计称为Actor模型。这是并行计算的数学模型,基本上将一切都视为参与者。参与者可以相互传递消息,并且响应消息可以做出本地决策。这是由卡尔·休伊特(Carl Hewitt)首次提出的,并启发了许多编程语言。
Scala用于并发编程的主要构造是参与者。Actor是Scala中的普通对象,我们可以通过实例化Actor类来创建。此外,Scala Actors库提供了许多有用的actor操作:
class myActor extends Actor {
def act() {
while(true) {
receive {
// Perform some action
}
}
}
}
在上面的示例中,在无限循环内对receive方法的调用将使actor挂起,直到消息到达为止。到达后,邮件将从参与者的邮箱中删除,并采取了必要的措施。
actor模型消除了并发编程的基本问题之一-------共享内存。参与者通过消息进行通信,并且每个参与者依次处理其专用邮箱中的消息。但是,我们通过线程池执行角色,而且我们已经看到,本地线程可能是重量级的,因此数量有限。
当然,这里还有其他模式可以为我们提供帮助,稍后将介绍这些模式!
基于事件的设计明确解决了本机线程生成和操作成本高昂的问题。基于事件的设计之一是事件循环。事件循环与事件提供程序和一组事件处理程序一起使用。在这种设置中,事件循环在事件提供程序上阻塞,并在到达时将事件调度到事件处理程序。
基本上,事件循环不过是事件分配器!事件循环本身可以仅在单个本机线程上运行。那么,事件循环中到底发生了什么?让我们来看一个非常简单的事件循环的伪代码作为示例:
while(true) {
events = getEvents();
for(e in events)
processEvent(e);
}
基本上,我们的事件循环所要做的就是不断寻找事件,并在发现事件后对其进行处理。该方法确实很简单,但可以从事件驱动的设计中受益。
使用此设计构建并发应用程序可为应用程序提供更多控制。而且,它消除了多线程应用程序的一些典型问题,例如死锁。
JAVAScript实现事件循环以提供异步编程。它维护一个调用堆栈以跟踪要执行的所有功能。它还维护一个事件队列,用于发送新功能进行处理。事件循环不断检查调用堆栈,并从事件队列中添加新功能。所有异步调用都会分派到通常由浏览器提供的Web API。
事件循环本身可以在单个线程上运行,但是Web API提供了单独的线程。
在非阻塞算法中,一个线程的挂起不会导致其他线程的挂起。我们已经看到,我们的应用程序中只能有数量有限的本机线程。现在,阻塞在线程上的算法明显降低了吞吐量, 并阻止了我们构建高度并发的应用程序。
非阻塞算法始终使用底层硬件提供的比较交换原子原语。这意味着硬件将比较存储位置的内容与给定值,并且只有它们相同时,才会将值更新为新的给定值。这看起来很简单,但实际上为我们提供了一个原子操作,否则将需要同步。
这意味着我们必须编写使用此原子操作的新数据结构和库。这为我们提供了多种语言的大量免等待和免锁实现。Java具有几种非阻塞数据结构,例如AtomicBoolean,AtomicInteger,AtomicLong和AtomicReference。
考虑一个有多个线程试图访问相同代码的应用程序:
boolean open = false;
if(!open) {
// Do Something
open=false;
}
显然,上面的代码不是线程安全的,并且它在多线程环境中的行为可能是不可预测的。我们的选择是将这段代码与锁同步,或者使用原子操作:
AtomicBoolean open = new AtomicBoolean(false);
if(open.compareAndSet(false, true) {
// Do Something
}
如我们所见,使用像AtomicBoolean这样的非阻塞数据结构可以帮助我们编写线程安全的代码,而不会沉迷于锁的弊端!
我们已经看到,可以通过多种方式构造并发模块。尽管编程语言确实有所作为,但主要是底层操作系统如何支持该概念。但是,由于本机线程支持的基于线程的并发在可伸缩性方面遇到了新的障碍,因此我们始终需要新的选择。
我们可以使用的一种解决方案是绿色线程。绿色线程是由运行时库调度的线程,而不是由底层操作系统本地调度的线程。尽管这并不能解决基于线程的并发中的所有问题,但在某些情况下,它肯定可以为我们提供更好的性能。
现在,除非我们选择使用的编程语言支持绿色线程,否则使用绿色线程并非易事。并非每种编程语言都具有此内置支持。同样,我们可以通过不同的编程语言以非常独特的方式来实现我们所谓的绿色线程。让我们来看一些可用的选项。
Go编程语言中的Goroutine 是轻量级线程。它们提供可以与其他功能或方法同时运行的功能或方法。Goroutines 非常便宜,因为它们从开始只占用几千字节的堆栈大小。
最重要的是,goroutines与较少数量的本机线程复用。此外,goroutine使用通道相互通信,从而避免了对共享内存的访问。我们几乎获得了所需的一切,然后猜测-什么都不做!
在Erlang中,每个执行线程称为一个进程。但是,这与我们到目前为止讨论的过程不太一样!Erlang进程重量轻,内存占用少,并且创建和处理速度快,调度开销低。
在幕后,Erlang进程不过是运行时为之调度的功能。而且,Erlang进程不共享任何数据,它们通过消息传递相互通信。这就是为什么我们首先称这些“过程”的原因!
Java并发的故事一直在不断发展。Java确实从一开始就对绿色线程(至少对Solaris操作系统)提供了支持。但是,由于障碍超出了本教程的范围,因此已停止使用。
从那时起,Java中的并发全部与本地线程有关,以及如何巧妙地使用它们!但是出于显而易见的原因,我们可能很快就会在Java中有了一个新的并发抽象,称为光纤。Project Loom建议将延续与纤维一起引入,这可能会改变我们用 Java 编写并发应用程序的方式!
这只是对不同编程语言中可用功能的简要介绍。其他编程语言还尝试了更多有趣的方式来处理并发。
此外,值得注意的是,在设计高度并发的应用程序时,上一节中讨论的设计模式与编程语言对类似绿线程的抽象的支持的组合可能会非常强大。
实际应用程序通常具有多个组件,这些组件通过导线相互交互。我们通常通过Internet对其进行访问,它包含多种服务,例如代理服务,网关,Web服务,数据库,目录服务和文件系统。
在这种情况下,我们如何确保高并发性?让我们探索其中的一些层以及构建高度并发应用程序所具有的选项。
构建高并发应用程序的关键是使用此处讨论的一些设计概念。我们需要为工作选择合适的软件-----已经结合了其中一些实践的软件。
Web通常是用户请求到达的第一层,因此在此处不可避免地需要进行高并发性设置。让我们看看其中的一些选项:
在设计应用程序时,有多种工具可帮助我们构建高并发性,先检查一下其中一些可用的库和框架:
没有数据就没有完整的应用程序,数据来自持久性存储。当我们讨论有关数据库的高并发性时,大多数重点仍放在NoSQL系列上。这主要是由于NoSQL数据库可以提供线性可伸缩性,但是在关系型变量中却很难实现。让我们看一下数据层的两个流行工具:
现代世界中没有任何旨在实现高并发性的Web应用程序能够承受每次访问数据库的负担。这就让我们选择了一个缓存-----最好是可以支持我们高度并发的应用程序的内存中缓存:
当然,在我们追求构建高度并发的应用程序时,我们几乎没有涉及任何可用的内容。重要的是要注意,除可用软件外,我们的要求还应指导我们创建适当的设计。这些选项中的某些选项可能适用,而其他选项可能不合适。
而且,别忘了还有更多可用的选项可能更适合我们的要求。
在本文中,我们讨论了并发编程的基础。我们了解了并发的一些基本方面及其可能导致的问题。此外,我们演示了一些设计模式,这些模式可以帮助我们避免并发编程中的典型问题。
最后,我们介绍了一些可用于构建高度并行的端到端应用程序的框架,库和软件。
分享一份自己整理好的Java学习资料,里面包含了:分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、MySQL、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货