`
ajax_xu
  • 浏览: 155853 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

Netty系列之Netty并发编程分析(转载)

 
阅读更多
文章地址  http://www.infoq.com/cn/articles/netty-concurrent-programming-analysis/

1. JAVA内存模型与多线程编程

1.1. 硬件的发展和多任务处理

随着硬件特别是多核处理器的发展和价格的下降,多任务处理已经是所有操作系统必备的一项基本功能。在同一个时刻让计算机做多件事情,不仅仅是因为处理器的并行计算能力得到了很大提升,还有一个重要的原因是计算机的存储系统、网络通信等IO性能与CPU的计算能力差距太大,导致程序的很大一部分执行时间被浪费在IO wait上面,CPU的强大运算能力没有得到充分利用。

Java提供了很多类库和工具用于降低并发编程的门槛,提升开发效率,一些开源的第三方软件也提供了额外的并发编程类库方便JAVA开发者,使开发者将重心放在业务逻辑的设计和实现上,而不是处处考虑线程的同步和锁。但是,无论并发类库设计的如何完美,它都无法完全满足使用者的需求,对于一个高级JAVA程序员来说,如果不懂得JAVA并发编程的内幕,只懂得使用一些简单的并发类库和工具,是无法完全驾驭JAVA多线程这匹野马的。

1.2. JAVA内存模型

JVM规范定义了JAVA内存模型(Java Memory Model)来屏蔽掉各种操作系统、虚拟机实现厂商和硬件的内存访问差异,以实现JAVA程序在所有操作系统和平台上能够实现一次编写、到处运行的效果。

Java内存模型的制定既要严谨,保证语义无歧义,另外,也要制定的尽量宽松一些,允许各硬件和虚拟机实现厂商有足够的灵活性来充分利用硬件的特性提升JAVA的内存访问性能。随着JDK的发展,Java的内存模型已经逐渐成熟起来。

工作内存和主内存
Java内存模型规定所有的变量都存储在主内存中(JVM内存的一部分),每个线程有自己独立的工作内存,它保存了被该线程使用的变量的主内存拷贝,线程对这些变量的操作都在自己的工作内存中进行,不能直接操作主内存和其它工作内存中存储的变量或者变量副本,线程间的变量访问需通过主内存来完成,三者的关系如下图所示:



图1.2.1 JAVA内存访问模型

内存交互协议
JAVA内存模型定义了八种操作来完成主内存和工作内存的变量访问,具体如下:

lock:主内存变量,把一个变量标识为某个线程独占的状态;
unlock:主内存变量,把一个处于锁定状态变量释放出来,被释放后的变量才可以被其它线程锁定;
read:主内存变量,把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
load:工作内存变量,把read读取到的主内存中的变量值放入工作内存的变量拷贝中;
use:工作内存变量,把工作内存中变量的值传递给java虚拟机执行引擎,每当虚拟机遇到一个需要使用到变量值的字节码指令时将会执行该操作;
assign:工作内存变量,把从执行引擎接收到的变量的值赋值给工作变量,每当虚拟机遇到一个给变量赋值的字节码时将会执行该操作;
store:工作内存变量,把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用;
write:主内存变量,把store操作从工作内存中得到的变量值放入主内存的变量中。
JAVA的线程
并发的实现可以通过多种方式来实现,例如:单进程-单线程模型,通过在一台服务器上启多个进程实现多任务的并行处理。但是在JAVA语言中,通过是通过单进程-多线程的模型进行多任务的并发处理。因此,我们有必要熟悉一下JAVA的线程。

大家都知道,线程是比进程更轻量级的调度执行单元,它可以把进程的资源分配和调度执行分开,各个线程可以共享内存、IO等操作系统资源,但是又能够被操作系统发的内核线程或者进程执行。各线程可以独立的启动、运行和停止,实现任务的解耦。

主流的操作系统都提供了线程实现,目前实现线程的方式主要有三种,分别是:

内核线程(KLT)实现,这种线程由内核来完成线程切换,内核通过线程调度器对线程进行调度,并负责将线程任务映射到不同的处理器上;
用户线程实现(UT),通常情况下,用户线程指的是完全建立在用户空间线程库上的线程,用户线程的创建、启动、运行、销毁和切换完全在用户态中完成,不需要内核的帮助,因此执行性能更高;
混合实现:将内核线程和用户线程混合在一起使用的方式。
由于虚拟机规范并没有强制规定JAVA的线程必须使用哪种方式实现,因此,不同的操作系统实现的方式也可能存在差异。对于SUN的JDK,在Windows和Linux操作系统上采用了内核线程的实现方式,在Solaris版本的JDK中,提供了一些专有的虚拟机线程参数,用于设置使用哪种线程模型。

2. Netty的并发编程分析

在Java技术领域,网络通信和多线程并发编程是相对较高级和难掌握的领域,作为高性能的NIO通信框架,线程模型对Netty的性能影响非常大,Netty的高性能是建立在灵活和高效的并发编程基础之上。

通过学习Netty的多线程并发编程技巧,对于我们掌握并在实践中灵活应用Java多线程编程来提升系统性能带来很大的帮助。

2.1. 对共享的可变数据进行正确的同步

关键字synchronized 可以保证在同一时刻,只有一个线程可以执行某一个方法或者代码块。同步的作用不仅仅是互斥,它的另一个作用就是共享可变性,当某个线程修改了可变数据并释放锁后,其它的线程可以获取被修改变量的最新值。如果没有正确的同步,这种修改对其它线程是不可见的。

下面我们就通过对Netty的源码进行分析,看看Netty是如何对并发可变数据进行正确同步的。

以ServerBootstrap为例进行分析,首先看它的option方法:



这个方法的作用是设置ServerBootstrap的ServerSocketChannel的Socket属性,它的属性集定义如下:



由于是非线程安全的LinkedHashMap,所以如果多线程创建、访问和修改LinkedHashMap时,必须在外部进行必要的同步,LinkedHashMap的API DOC对于线程安全的说明如下:



由于ServerBootstrap是被使用者创建和使用的,我们无法保证它的方法和成员变量不被并发访问,因此,作为成员变量的options必须进行正确的同步。由于考虑到锁的范围需要尽可能的小,我们对传参的option和value的合法性判断不需要加锁。因此,代码才对两个判断分支独立加锁,保证锁的范围尽可能的细粒度。

Netty加锁的地方非常多,大家在阅读代码的时候可以仔细体会下,为什么有的地方要加锁,有的地方有不需要?如果不需要,为什么?当你对锁的真谛理解以后,对于这些锁的使用时机和技巧理解起来就非常容易了。

2.2. 正确的使用锁

对于很多刚接触多线程编程的开发者,意识到了并发访问可变变量需要加锁,但是对于锁的范围、加锁的时机和锁的协同缺乏认识,往往会导致一些问题,下面我就结合Netty的代码来讲解下这方面的知识。

打开ForkJoinTask,我们学习一些多线程同步和协作方面的技巧,先看下当条件不满足时阻塞某个任务,直到条件满足后再继续执行,代码如下:



重点看下红框中的代码,首先通过循环检测的方式对状态变量status进行判断,当它的状态大于等于0时,执行wait(),阻塞当前的调度线程,直到status小于0,唤醒所有被阻塞的线程,继续执行。这个方法有三个多线程的编程技巧需要说明:

wait方法别用来使线程等待某个条件,它必须在同步块内部被调用,这个同步块通常会锁定当前对象实例。下面是这个模式的标准使用方式:
synchronized(this)
   {
    While(condition)
        Object.wait;
......
}
始终使用wait循环来调用wait方法,永远不要在循环之外调用wait方法。原因是尽管条件并不满足被唤醒条件,但是由于其它线程意外调用notifyAll()方法会导致被阻塞线程意外唤醒,此时执行条件并不满足,它将破坏被锁保护的约定关系,导致约束失效,引起意想不到的结果;
唤醒线程,应该使用notify还是notifyAll,当你不知道究竟该调用哪个方法时,保守的做法是调用notifyAll唤醒所有等待的线程。从优化的角度看,如果处于等待的所有线程都在等待同一个条件,而每次只有一个线程可以从这个条件中被唤醒,那么就应该选择调用notify。
当多个线程共享同一个变量的时候,每个读或者写数据的操作方法都必须加锁进行同步,如果没有正确的同步,就无法保证一个线程所做的修改被其它线程可见。未能同步共享变量会造成程序的活性失败和安全性失败,这样的失败通常是难以调试和重现的,它们可能间歇性的出问题,也可能随着并发的线程个数而失败,也可能在不同的虚拟机或者操作系统上存在不同的失败概率。因此,我们务必要保证锁的正确使用。下面这个案例,就是个典型的错误应用:

int size = 0;
public synchronized void increase()
{
     size++;
}
public int current()
{
      Return size;
}
2.3. volatile的正确使用

在实际工作中,我发现即便是一些经验丰富的JAVA设计师,对于volatile和多线程编程的认识仍然存在误区。其实,volatile的使用非常简单,只要理解了JAVA的内存模型和多线程编程基础知识,正确使用volatile是不存在任何问题的,下面我们结合Netty的源码,对volatile的正确使用进行说明。

打开NioEventLoop的代码,我们来看下控制IO操作和其它任务运行比例的ioRatio,它是int类型的变量,定义如下:



我们发现,它被定义为volatile,为什么呢?首先对volatile关键字进行说明,然后再结合Netty的代码进行分析。

关键字volatile是JAVA提供的最轻量级的同步机制,JAVA内存模型对volatile专门定义了一些特殊的访问规则,下面我们就看下它的规则:

当一个变量被volatile修饰后,它将具备两种特性:

1. 线程可见性:当一个线程修改了被volatile修饰的变量后,无论是否加锁,其它线程都可以立即看到最新的修改,而普通变量却做不到这点;

2. 禁止指令重排序优化,普通的变量仅仅保证在该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果,而不能保证变量赋值操作的顺序与程序代码的执行顺序一致。举个简单的例子说明下指令重排序优化问题:



我们预期程序会在3S后停止,但是实际上它会一直执行下去,原因就是虚拟机对代码进行了指令重排序和优化,优化后的指令如下:

if (!stop)
While(true)
   ......
重排序后的代码是无法发现stop被主线程修改的,因此无法停止运行。如果要解决这个问题,只要将stop前增加volatile修饰符即可,代码修改如下:



再次运行,我们发现3S后程序退出,达到了预期效果,使用volatile解决了如下两个问题:

main线程对stop的修改在workThread线程中可见,也就是说workThread线程立即看到了其它线程对于stop变量的修改;
禁止指令重排序,防止因为重排序导致的并发访问逻辑混乱。
一些人错误的认为使用volatile可以代替传统锁,提升并发性能,这个认识是错误的,volatile仅仅解决了可见性的问题,但是它并不能保证互斥性,也就是说多个线程并发修改某个变量时,依旧会产生多线程问题。因此,不能靠volatile来完全替代传的锁。

根据经验总结,volatile最适合使用的地方是一个线程写、其它线程读的场合,如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替。

讲了volatile的原理之后,我们继续对Netty的源码做分析,上面我们说到了ioRatio被定义成volatile,下面看看代码为啥这样定义:



通过代码分析我们发现,在NioEventLoop线程中,ioRatio并没有被修改,它是只读操作。那既然没有修改,为啥要定义成volatile呢?我们继续看代码,我们发现NioEventLoop提供了重新设置IO执行时间比例的公共方法,接口如下:



首先,NioEventLoop线程没有调用该方法,说明调整IO执行时间比例是外部发起的操作,通常是由业务的线程调用该方法,重新设置该参数。这样就形成了一个线程写、一个线程读,根据前面针对volatile的应用总结,此时可以使用volatile来代替传统的synchronized关键字提升并发访问的性能。

Netty中大量使用了volatile来修改成员变量,如果理解了volatile的应用场景,读懂Netty volatile的相关代码还是比较容易的。

2.4. CAS指令和原子类

互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步被称为阻塞同步,它属于一种悲观的并发策略,我们称之为悲观锁。随着硬件和操作系统指令集的发展和优化,产生了非阻塞同步,被称为乐观锁。简单的说就是先进行操作,操作完成之后再判断下看看操作是否成功,是否有并发问题,如果有进行失败补偿,如果没有就算操作成功,这样就从根本上避免了同步锁的弊端。

目前,在JAVA中应用最广泛的非阻塞同步就是CAS,在IA64、X86指令集中通过cmpxchg指令完成CAS功能,在sparc-TSO中由case指令完成,在ARM和PowerPC架构下,需要使用一对Idrex/strex指令完成。

从JDK1.5以后,可以使用CAS操作,该操作由sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等方法包装提供。通常情况下sun.misc.Unsafe类对于开发者是不可见的,因此,JDK提供了很多CAS包装类简化开发者的使用,例如AtomicInteger等。

下面,结合Netty的源码,我们对于原子类的正确使用进行详细说明:

我们打开ChannelOutboundBuffer的代码,看看如何对发送的总字节数进行计数和更新操作,先看定义:



首先定义了一个volatile的变量,它可以保证某个线程对于totalPendingSize的修改可以被其它线程立即访问到,但是,它无法保证多线程并发修改的安全性。紧接着又定义了一个AtomicIntegerFieldUpdater类型的变量WTOTAL_PENDING_SIZE_UPDATER,实现totalPendingSize的原子更新,也就是保证totalPendingSize的多线程修改并发安全性,我们重点看下AtomicIntegerFieldUpdater的API说明:



从API的说明我们可以看出来,它主要用于实现volatile修饰的int变量的原子更新操作,对于使用者,必须通过类似compareAndSet或者set或者与这些操作等价的原子操作来保证更新的原子性,否则会导致问题。

我们继续看代码,当执行write操作外发消息的时候,需要对外发的消息字节数进行统计汇总,由于调用write操作的既可以是IO线程,也可以是业务的线程,也可能由业务线程池多个工作线程同时执行发送任务,因此,统计操作是多线程并发的,这也就是为什么要将计数器定义成volatile并使用原子更新类进行原子操作,下面,我们看下计数的代码:



首先,我们发现计数操作并没有实现锁,而是使用了CAS自旋操作,通过

TOTAL_PENDING_SIZE_UPDATER.compareAndSet(this, oldValue, newWriteBufferSize)来判断本次原子操作是否成功,如果成功则退出循环,代码继续执行;如果失败,说明在本次操作的过程中计数器已经被其它线程更新成功,我们需要进入循环,首先,对oldValue进行更新,代码如下:

oldValue = totalPendingSize;
然后重新对更新值进行计算:

newWriteBufferSize = oldValue + size;
继续循环进行CAS操作,直到成功。它跟AtomicInteger的compareAndSet操作类似。

使用JAVA自带的Atomic原子类,可以避免同步锁带来的并发访问性能降低的问题,减少犯错的机会,因此,Netty中对于int、long、boolean等大量使用其原子类,减少了锁的应用,降低了频繁使用同步锁带来的性能下降。

2.5. 线程安全类的应用

在JDK1.5的发行版本中,Java平台新增了java.util.concurrent,这个包中提供了一系列的线程安全集合、容器和线程池,利用这些新的线程安全类可以极大的降低Java多线程编程的难度,提升开发效率。

新的并发编程包中的工具可以分为如下四类:

线程池Executor Framework以及定时任务相关的类库,包括Timer等;
并发集合,包括List、Queue、Map和Set等;
新的同步器,例如读写锁ReadWriteLock等;
新的原子包装类,例如AtomicInteger等。
在实际编码过程中,我们建议通过使用线程池、Task(Runnable/Callable)、原子类和线程安全容器来代替传统的同步锁、wait和notify,提升并发访问的性能、降低多线程编程的难度。

下面,我们针对新的线程并发包在Netty中的应用进行分析和说明,以期为大家的应用提供指导。

首先,我们看下线程安全容器在Netty中的应用,NioEventLoop是IO线程,负责网络读写操作,它同时也执行一些非IO的任务,例如事件通知、定时任务执行等,因此,它需要一个任务队列来缓存这些Task,它的任务队列定义如下:



它是一个ConcurrentLinkedQueue,我们看下它的API 说明:



DOC文档明确说明这个类是线程安全的,因此,对它进行读写操作不需要加锁,下面我们继续看下队列中增加一个任务:



读取任务,也不需要加锁:



JDK的线程安全容器底层采用了CAS、volatile和ReadWriteLock实现,相比于传统重量级的同步锁,采用了更轻量、细粒度的锁,因此,性能会更高。采用这些线程安全容器,不仅仅能提升多线程并发访问的性能,还能降低开发难度。

下面我们看看线程池在Netty中的应用,打开SingleThreadEventExecutor看下它是如何定义和使用线程池的:

首先定义了一个标准的线程池用于执行任务:



接着对它赋值并且进行初始化操作:



执行任务:



我们发现,实际上是执行任务就是先把任务加入到任务队列中,然后判断线程是否已经启动循环执行,如果不是需要启动线程,启动线程代码如下:



实际上就是执行当前线程的run方法,循环从任务队列中获取Task并执行,我们看下它的子类NioEventLoop的run方法就能一目了然:



如红框中所示,循环从任务队列中获取任务并执行:



Netty对JDK的线程池进行了封装和改造,但是,本质上仍然是利用了线程池和线程安全队列简化了多线程编程。

2.6. 读写锁的应用

JDK1.5新的并发编程工具包中新增了读写锁,它是个轻量级、细粒度的锁,合理的使用读写锁,相比于传统的同步锁,可以提升并发访问的性能和吞吐量,在读多写少的场景下,使用同步锁比同步块性能高一大截。

尽管JDK1.6之后,随着JVM团队对JIT即使编译器的不断优化,同步块和读写锁的性能差距缩小了很多;但是,读写锁的应用依然非常广泛,例如,JDK的线程安全List CopyOnWriteArrayList就是基于读写锁实现的,代码如下:



下面,我们对Netty中的读写锁应用进行分析,让大家掌握读写锁的用法,打开HashedWheelTimer代码,读写锁定义如下:



当新增一个定时任务的时候使用了读锁,用于同步wheel的变化,由于读锁是



共享锁,所以当有多个线程同时调用newTimeout的时候,并不会互斥,这样,就提升了并发读的性能。

获取并删除所有过期的任务时,由于要从迭代器中删除任务,所以使用了写锁:



读写锁的使用总结:

主要用于读多写少的场景,用来替代传统的同步锁,以提升并发访问性能;
读写锁是可重入、可降级的,一个线程获取读写锁后,可以继续递归获取;从写锁可以降级为读锁,以便快速释放锁资源;
ReentrantReadWriteLock支持获取锁的公平策略,在某些特殊的应用场景下,可以提升并发访问的性能,同时兼顾线程等待公平性;
读写锁支持非阻塞的尝试获取锁,如果获取失败,直接返回false,而不是同步阻塞,这个功能在一些场景下非常有用。例如多个线程同步读写某个资源,当发生异常或者需要释放资源的时候,由哪个线程释放是个挑战,因为某些资源不能重复释放或者重复执行,这样,可以通过tryLock方法尝试获取锁,如果拿不到,说明已经被其它线程占用,直接退出即可;
获取锁之后一定要释放锁,否则会发生锁溢出异常。通常的做法是通过finally块释放锁。如果是tryLock,获取锁成功才需要释放锁。
2.7. 线程安全性的文档说明

当一个类的方法或者成员变量被并发使用的时候,这个类的行为如何,是该类与其客户端程序建立约定的重要组成部分。如果没有在这个类的文档中描述其行为的并发情况,使用这个类的程序员不得不做出某种假设。如果这些假设是错误的,这个程序就缺少必要的同步保护,会导致意想不到的并发问题,这些问题通常都是隐蔽和调试困难的。如果同步过度,会导致意外的性能下降,无论是发生何种情况,缺少线程安全性的说明文档,都会令开发人员非常沮丧,他们会对这些类库的使用小心翼翼,提心吊胆。

在Netty中,对于一些关键的类库,给出了线程安全习惯的API DOC,尽管Netty的线程安全性并不是非常完善,但是,相比于一些做的更糟糕的产品,它还是迈出了重要一步。



由于ChannelPipeline的应用非常广泛,因此,在API中对它的线程安全性进行了详细的说明,这样,开发者在调用ChannelPipeline的API时,就不要再额外的考虑线程同步和并发问题。

3. 附录

3.1. 总结

本文并不试图面面俱到的涵盖所有JAVA多线程编程的知识点,如果读者希望了解更多的多线程编程知识,建议阅读 Joshua Bloch的《Java Concurrency in Practive》。

事实上,多线程编程作为一个难点,也是阻拦读者深入理解Netty架构和源码的一个拦路虎,如果你的基本功不够扎实,对形形色色的多线程编程技术无法透彻理解,就很难说能够真正理解Netty。当你带着这些疑问去定位Netty深层次问题的时候,也是困难重重。

简言之,如果你不能够精通JAVA的多线程编程,对于很多开源框架的深入理解都会非常困难。

本文通过对Netty中主要使用的多线程编程技术进行归纳、汇总和分析,以期让读者更快的熟悉多线程编程的常用知识点和技巧,加深大家对Netty的理解。

3.2. 作者简介

李林锋,2007年毕业于东北大学,2008年进入华为公司从事高性能通信软件的设计和开发工作,有6年NIO设计和开发经验,精通Netty、Mina等NIO框架,Netty中国社区创始人和Netty框架推广者,《Netty权威指南》作者。
分享到:
评论

相关推荐

    java项目,课程设计-ssm病人跟踪治疗信息管理系统

    病人跟踪治疗信息管理系统采用B/S模式,促进了病人跟踪治疗信息管理系统的安全、快捷、高效的发展。传统的管理模式还处于手工处理阶段,管理效率极低,随着病人的不断增多,传统基于手工管理模式已经无法满足当前病人需求,随着信息化时代的到来,使得病人跟踪治疗信息管理系统的开发成了必然。 本网站系统使用动态网页开发SSM框架,Java作为系统的开发语言,MySQL作为后台数据库。设计开发了具有管理员;首页、个人中心、病人管理、病例采集管理、预约管理、医生管理、上传核酸检测报告管理、上传行动轨迹管理、分类管理、病人治疗状况管理、留言板管理、系统管理,病人;首页、个人中心、病例采集管理、预约管理、医生管理、上传核酸检测报告管理、上传行动轨迹管理、病人治疗状况管理,前台首页;首页、医生、医疗资讯、留言反馈、个人中心、后台管理、在线咨询等功能的病人跟踪治疗信息管理系统。在设计过程中,充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。

    liunx project 5

    liunx project 5

    PostgreSQL DBA实战视频教程(完整10门课程合集)

    分享课程——PostgreSQL DBA实战视频教程(完整10门课程合集)

    计算机科学基础期末考试试题

    计算机科学基础期末考试试题

    c语言实验设备管理系统

    练习与巩固《C语言程序设计》理论知识,通过实践检验和提高实际能力,进一步培养自己综合分析问题和解决问题的能力。掌握运用C语言独立地编写调试应用程序和进行其它相关设计的技能。

    提高图像在低光照条件下的清晰度和可见性,使用CNN的图像重建网络,来实现亮度调节,可用于小白学习

    1. 数据集资源 公开低光照数据集:用于模型训练的低光照图像数据集,这些数据集包含了多种低光照条件下的图像,并附有增强后的高质量图像。 合成数据:在不足数据的情况下,可以通过对高亮度图像进行暗化处理生成低光图像对,以增强数据量。 自建数据集:对于特定场景,如安防、医疗等,可以拍摄或收集特定条件下的低光照图像来创建数据集,满足特定应用需求。 2. 硬件资源 GPU:大规模模型训练需要高性能计算,以支持大规模图像处理和神经网络训练。 数据存储:由于图像数据较大,需要大容量的存储设备如HDD或SSD来存储数据集及中间结果。 3. 深度学习框架及工具 PyTorch:支持构建和训练神经网络模型,尤其适合卷积神经网络(CNN)和生成对抗网络(GAN)的实现。 CUDA和cuDNN:为GPU加速库,在模型训练时可显著提升运行效率。

    双哥微服务.md

    双哥微服务

    fb000f5e-12c5-a46b-102a-f08bdfa015f1.json

    fb000f5e-12c5-a46b-102a-f08bdfa015f1.json

    C#ASP.NET跑腿服务网站源码数据库 Access源码类型 WebForm

    ASP.NET跑腿服务网站源码 开发环境 :Asp.net + VS2010 + C# + ACCESS 网站介绍: 适合人群:跑腿服务行业公司,服务资讯公司或者其他行业企业、 做服务行业建站的技术人员、技术人员学习参考都行。 技术特点:非常清爽大气的网站,界面华丽,工整,采用全div布局, 含flash图片切换功能,强大的后台信息管理功能。 功能介绍: 后台功能:系统参数设置(网站标题,关键字,内容,站长联系方式等)、系统栏目频道设置、新闻管 理、服务项目管理、公司介绍内容管、系统模版管理(可管理前台页面模版内容,具体到头部页面,底 部页面,首页,内容页,新网页等)、系统日志管理、系统管理员管理、频道管理(频道类型、频道内 容、内容发布以及编辑)。 后台地址:网址/admin/login.aspx 账户:admin 密码:admin888

    KCP一个快速可靠的ARQ协议.zip

    c语言

    【小程序毕业设计】基于微信小程序的物流运输(仓储)系统开发与设计源码(完整前后端+mysql+说明文档+LW).zip

    环境说明: 开发语言:Java/php JDK版本:JDK1.8 数据库:mysql 5.7及以上 数据库工具:Navicat11及以上 开发软件:eclipse/idea 小程序框架:uniapp/原生小程序 开发工具:HBuilder X/微信开发者

    计算机中 人工智能的七大应用领域

    人工智能(Artificial Intelligence,缩写为AI)是一种通过计算机程序模拟人类智能与行为的技术和理论。它可以用于各种领域,例如:自动驾驶、机器翻译、语音识别、图像识别、医疗诊断等。近年来,人工智能逐渐成为了技术界和商业领域的热门话题。

    ESP32ESP32C2ESP32C3ESP32C6ESP8266的AT应用.zip

    c语言

    基于JAVA实现的离散数学题库管理系统.zip

    基于JAVA实现的离散数学题库管理系统

    【图像压缩】基于matlab GUI低比特率图像压缩(含比特率 压缩包 信噪比)【含Matlab源码 9132期】.mp4

    Matlab领域上传的视频均有对应的完整代码,皆可运行,亲测可用,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作

    (源码)基于C++的MiniSQL数据库系统.zip

    # 基于C++的MiniSQL数据库系统 ## 项目简介 MiniSQL是一个轻量级的关系型数据库管理系统,旨在提供基本的SQL解析和执行功能。该项目参考了CMU15445 BusTub框架,并在其基础上进行了修改和扩展,以兼容原MiniSQL实验指导的要求。MiniSQL支持缓冲池管理、索引管理、记录管理等核心功能,并提供了简单的交互式SQL解析和执行引擎。 ## 项目的主要特性和功能 1. 缓冲池管理实现了一个高效的缓冲池管理器,用于缓存磁盘上的数据页,以提高数据访问速度。 2. 索引管理支持B+树索引,提供高效的插入、删除和查找操作。 3. 记录管理实现了记录的插入、删除、更新和查询功能,支持持久化存储。 4. 元数据管理提供了表和索引的元数据管理功能,支持持久化存储和检索。 5. 并发控制实现了基本的锁管理器,支持事务的并发控制。 6. 查询执行提供了简单的查询执行引擎,支持基本的SQL语句解析和执行。 ## 安装使用步骤

    社会科学研究Top 10,000 Papers数据解析论文名称被引次数下载次数等

    社会科学研究Top 10,000 Papers数据解析被引次数下载次数等 一、数据背景与来源 该数据集来源于SSRN(Social Science Research Network)的社会科学研究Top 10,000 Papers,是根据多种学术影响力指标统计得出的,在其平台上最受关注的前10,000篇学术论文的汇总。这些数据反映了国际研究领域的热点话题和发展趋势,对于国内学者研究者来说,是了解社科领域研究进展的重要窗口。 二、数据内容概览 样本数量:数据集包含10,000条记录,每条记录代表一篇在SSRN平台上具有高影响力的学术论文。 论文范围:涵盖社会科学研究的各个领域,包括但不限于经济学、政治学、社会学、心理学、教育学等。 关键指标: 数据下载次数:反映了论文的受欢迎程度和研究者对其内容的关注度。 引用次数:体现了论文在学术界的认可度和影响力,是评估论文质量的重要指标之一。 Rank Paper Total New Downloads Total # of Downloads Total # of Citations # of Authors

    【北京理工大学-2024研报】中国碳达峰碳中和时间表与路线图研究.pdf

    行业研究报告、行业调查报告、研报

    基于 Java+Mysql 实现的企业人事管理系统【课程设计/毕业设计】(源码+设计报告)

    【作品名称】:基于 Java+Mysql 实现的企业人事管理系统【课程设计/毕业设计】(源码+设计报告) 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】: [1]管理员可以对员工的基本信息的增删改查,普通员工仅可查看; [2]对公司里所有员工进行分配工号,并进行基本信息的录入; [3]对新聘用的员工,将其信息加入到员工档案记录中; [4]对于解聘的员工,将其信息从员工档案记录中删除。 [5]当员工信息发生变动时,修改员工档案记录中相应的属性。 (三)员工岗位信息管理 [1]对公司里所有员工的职务及岗位信息(岗位职责)进行记录; [2]记录员工调动前后的具体职务,以及调动时间。 (四)考勤管理 [1]对员工上班刷卡的记录进行统一编号;登记员工上班时间(准时、迟到)。 [2]对员工下班刷卡的记录进行统一编号;登记员工下班时间(准时、早 【资源声明】:本资源作为“参考资料”而不是“定制需求”,代码只能作为参考,不能完全复制照搬。需要有一定的基础看懂代码,自行调试代码并解决报错,能自行添加功能修改代码。

    (源码)基于Arduino编程的冰箱警报系统.zip

    # 基于Arduino编程的冰箱警报系统 ## 项目简介 这是一个基于Arduino编程的项目,通过连接到冰箱门开关的警报系统来提醒用户冰箱门开启时间过长。用户可以在设定的时间内关闭冰箱门,否则警报会响起。项目使用LCD控制器面板来设置和配置警报延迟时间。 ## 项目的主要特性和功能 1. 警报功能在冰箱门开启后,系统会开始计时,如果用户在设定的时间内未关闭冰箱门,警报会响起。 2. LCD配置面板使用LCD控制器面板设置和配置警报延迟时间。 3. 可配置警报时间用户可以根据需要调整警报延迟时间。 4. 状态显示LCD面板显示冰箱门的状态(开启关闭)。 ## 安装使用步骤 1. 下载并解压项目文件。 2. 准备硬件部件根据提供的物料清单(Bill of Materials)准备所需的硬件部件。 3. 连接硬件部件按照项目文档中的连接表(Connection Table)将硬件部件连接到Arduino主板和LCD控制器面板。

Global site tag (gtag.js) - Google Analytics