您好,欢迎访问一九零五行业门户网

如何在Java中将线程绑定到特定的CPU?

简介在现代计算机系统中,可以有多个cpu,每个cpu又可以有多核。为了充分利用现代cpu的功能,java中引入了多线程,不同的线程可以同时在不同cpu或者不同cpu核中运行。但是对于java程序猿来说创建多少线程是可以自己控制的,但是线程到底运行在哪个cpu上,则是一个黑盒子,一般来说很难得知。
但是如果是不同cpu核对同一线程进行调度,则可能会出现cpu切换造成的性能损失。一般情况下这种损失是比较小的,但是如果你的程序特别在意这种cpu切换带来的损耗,那么可以试试今天要讲的java thread affinity.
java thread affinity简介java thread affinity是用来将java代码中的线程绑定到cpu特定的核上,用来提升程序运行的性能。
很显然,要想和底层的cpu进行交互,java thread affinity一定会用到java和native方法进行交互的方法,jni虽然是java官方的java和native方法进行交互的方法,但是jni在使用起来比较繁琐。所以java thread affinity实际使用的是jna,jna是在jni的基础上进行改良的一种和native方法进行交互的库。
先来介绍cpu中几个概念,分别是cpu,cpu socket和cpu core。
首先是cpu,cpu的全称就是central processing unit,又叫做中央处理器,就是用来进行任务处理的关键核心。
那么什么是cpu socket呢?所谓socket就是插cpu的插槽,如果组装过台式机的同学应该都知道,cpu就是安装在socket上的。
cpu core指的是cpu中的核数,在很久之前cpu都是单核的,但是随着多核技术的发展,一个cpu中可以包含多个核,而cpu中的核就是真正的进行业务处理的单元。
如果你是在linux机子上,那么可以通过使用lscpu命令来查看系统的cpu情况,如下所示:
architecture: x86_64cpu op-mode(s): 32-bit, 64-bitbyte order: little endiancpu(s): 1on-line cpu(s) list: 0thread(s) per core: 1core(s) per socket: 1socket(s): 1numa node(s): 1vendor id: genuineintelcpu family: 6model: 94model name: intel(r) xeon(r) gold 6148 cpu @ 2.40ghzstepping: 3cpu mhz: 2400.000bogomips: 4800.00hypervisor vendor: kvmvirtualization type: fulll1d cache: 32kl1i cache: 32kl2 cache: 4096kl3 cache: 28160knuma node0 cpu(s): 0flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 arat
从上面的输出我们可以看到,这个服务器有一个socket,每个socket有一个core,每个core可以同时处理1个线程。
这些cpu的信息可以称为cpu layout。在linux中cpu的layout信息是存放在/proc/cpuinfo中的。
在java thread affinity中有一个cpulayout接口用来和这些信息进行对应:
public interface cpulayout { int cpus(); int sockets(); int corespersocket(); int threadspercore(); int socketid(int cpuid); int coreid(int cpuid); int threadid(int cpuid);}
根据cpu layout的信息, affinitystrategies提供了一些基本的affinity策略,用来安排不同的thread之间的分布关系,主要有下面几种:
same_core - 运行在同一个core中。 same_socket - 运行在同一个socket中,但是不在同一个core上。 different_socket - 运行在不同的socket中 different_core - 运行在不同的core上 any - 任何情况都可以
这些策略也都是根据cpulayout的socketid和coreid来进行区分的,我们以same_core为例,按下它的具体实现:
same_core { @override public boolean matches(int cpuid, int cpuid2) { cpulayout cpulayout = affinitylock.cpulayout(); return cpulayout.socketid(cpuid) == cpulayout.socketid(cpuid2) && cpulayout.coreid(cpuid) == cpulayout.coreid(cpuid2); } }
affinity策略可以有顺序,在前面的策略会首先匹配,如果匹配不上则会选择第二策略,依此类推。
affinitylock的使用接下来我们看下affinity的具体使用,首先是获得一个cpu的lock,在java7之前,我们可以这样写:
affinitylock al = affinitylock.acquirelock();try { // do some work locked to a cpu.} finally { al.release();}
在java7之后,可以这样写:
try (affinitylock al = affinitylock.acquirelock()) { // do some work while locked to a cpu.}
acquirelock方法可以为线程获得任何可用的cpu。这个是一个粗粒度的lock。如果想要获得细粒度的core,可以用acquirecore:
try (affinitylock al = affinitylock.acquirecore()) { // do some work while locked to a cpu.}
acquirelock还有一个bind参数,表示是否将当前的线程绑定到获得的cpu lock上,如果bind参数=true,那么当前的thread会在acquirelock中获得的cpu上运行。如果bind参数=false,表示acquirelock会在未来的某个时候进行bind。
上面我们提到了affinitystrategy,这个affinitystrategy可以作为acquirelock的参数使用:
public affinitylock acquirelock(affinitystrategy... strategies) { return acquirelock(false, cpuid, strategies); }
通过调用当前affinitylock的acquirelock方法,可以为当前的线程分配和之前的lock策略相关的affinitylock。
affinitylock还提供了一个dumplocks方法,用来查看当前cpu和thread的绑定状态。我们举个例子:
private static final executorservice es = executors.newfixedthreadpool(4, new affinitythreadfactory("bg", same_core, different_socket, any));for (int i = 0; i < 12; i++) es.submit(new callable<void>() { @override public void call() throws interruptedexception { thread.sleep(100); return null; } }); thread.sleep(200); system.out.println("\nthe assignment of cpus is\n" + affinitylock.dumplocks()); es.shutdown(); es.awaittermination(1, timeunit.seconds);
上面的代码中,我们创建了一个4个线程的线程池,对应的threadfactory是affinitythreadfactory,给线程池起名bg,并且分配了3个affinitystrategy。 意思是首先分配到同一个core上,然后到不同的socket上,最后是任何可用的cpu。
然后具体执行的过程中,我们提交了12个线程,但是我们的thread pool最多只有4个线程,可以预见, affinitylock.dumplocks方法返回的结果中只有4个线程会绑定cpu,一起来看看:
the assignment of cpus is0: cpu not available1: reserved for this application2: reserved for this application3: reserved for this application4: thread[bg-4,5,main] alive=true5: thread[bg-3,5,main] alive=true6: thread[bg-2,5,main] alive=true7: thread[bg,5,main] alive=true
从输出结果可以看到,cpu0是不可用的。其他7个cpu是可用的,但是只绑定了4个线程,这和我们之前的分析是匹配的。
接下来,我们把affinitythreadfactory的affinitystrategy修改一下,如下所示:
new affinitythreadfactory("bg", same_core)
表示线程只会绑定到同一个core中,因为在当前的硬件中,一个core同时只能支持一个线程的绑定,所以可以预见最后的结果只会绑定一个线程,运行结果如下:
the assignment of cpus is
0: cpu not available
1: reserved for this application
2: reserved for this application
3: reserved for this application
4: reserved for this application
5: reserved for this application
6: reserved for this application
7: thread[bg,5,main] alive=true
可以看到只有第一个线程绑定了cpu,和之前的分析相匹配。
使用api直接分配cpu上面我们提到的affinitylock的acquirelock方法其实还可以接受一个cpu id参数,直接用来获得传入cpu id的lock。这样后续线程就可以在指定的cpu上运行。
public static affinitylock acquirelock(int cpuid) { return acquirelock(true, cpuid, affinitystrategies.any); }
实时上这种affinity是存放在bitset中的,bitset的index就是cpu的id,对应的value就是是否获得锁。
先看下setaffinity方法的定义:
public static void setaffinity(int cpu) { bitset affinity = new bitset(runtime.getruntime().availableprocessors()); affinity.set(cpu); setaffinity(affinity); }
再看下setaffinity的使用:
long currentaffinity = affinitysupport.getaffinity();affinity.setaffinity(1l << 5); // lock to cpu 5.
注意,因为bitset底层是用long来进行数据存储的,所以这里的index是bit index,所以我们需要对十进制的cpu index进行转换。
以上就是如何在java中将线程绑定到特定的cpu?的详细内容。
其它类似信息

推荐信息