จะปรับขนาดเธรดตามแกน CPU ได้อย่างไร?


107

ฉันต้องการแก้ปัญหาทางคณิตศาสตร์ด้วยเธรดหลายเธรดใน Java ปัญหาทางคณิตศาสตร์ของฉันสามารถแบ่งออกเป็นหน่วยงานที่ฉันต้องการแก้ไขในหลายเธรด

ฉันไม่ต้องการให้เธรดจำนวนคงที่ใช้งานได้ แต่จำนวนเธรดที่ตรงกับจำนวนแกน CPU แทน ปัญหาของฉันคือฉันไม่พบบทแนะนำง่ายๆในอินเทอร์เน็ตสำหรับสิ่งนี้ ทั้งหมดที่ฉันพบคือตัวอย่างที่มีเธรดคงที่

จะทำได้อย่างไร? คุณสามารถให้ตัวอย่างได้หรือไม่?

คำตอบ:


120

คุณสามารถกำหนดจำนวนของกระบวนการที่สามารถใช้ได้กับโปรแกรม Java Virtual Machine โดยใช้วิธีการแบบคงที่รันไทม์availableProcessors เมื่อคุณกำหนดจำนวนโปรเซสเซอร์ที่มีแล้วให้สร้างเธรดจำนวนนั้นและแยกงานของคุณตามนั้น

อัปเดต : เพื่อชี้แจงเพิ่มเติมเธรดเป็นเพียงวัตถุใน Java ดังนั้นคุณสามารถสร้างได้เช่นเดียวกับที่คุณสร้างวัตถุอื่น ๆ สมมติว่าคุณเรียกใช้วิธีการข้างต้นและพบว่ามันส่งคืนโปรเซสเซอร์ 2 ตัว น่ากลัว ตอนนี้คุณสามารถสร้างลูปที่สร้างเธรดใหม่และแยกงานออกสำหรับเธรดนั้นและทำให้เธรดเริ่มทำงาน นี่คือรหัส psuedocode เพื่อแสดงความหมาย:

int processors = Runtime.getRuntime().availableProcessors();
for(int i=0; i < processors; i++) {
  Thread yourThread = new AThreadYouCreated();
  // You may need to pass in parameters depending on what work you are doing and how you setup your thread.
  yourThread.start();
}

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการสร้างชุดข้อความของคุณเองโปรดไปที่บทช่วยสอนนี้ นอกจากนี้คุณอาจต้องการดูที่การรวมเธรดสำหรับการสร้างเธรด


17
โดยพื้นฐานแล้วสิ่งนี้ถูกต้อง แต่โปรดระมัดระวังเกี่ยวกับประสิทธิภาพของโปรเซสเซอร์ที่วางตลาดด้วย "ไฮเปอร์เธรด" ของ Intel ใน quad-core สิ่งนี้จะส่งคืน 8 แทนที่จะเป็น 4 แต่ประสิทธิภาพของคุณอาจเริ่มลดลงหลังจาก 4 เธรด - ดังนั้นเกณฑ์มาตรฐานของฉันบอกฉันด้วย :)
xcut

สวัสดีตกลงไม่รู้ว่าเป็นไปได้ แต่เมื่อฉันแยกงานหนึ่งออกเป็นหลายหน่วยงานและฉันต้องการโซลูชันส่วนทั้งหมดสำหรับขั้นตอนสุดท้ายจะทำอย่างไร เมื่อฉันมี "yourThreads" หลายรายการฉันจะใช้ join () สำหรับสิ่งนี้ได้อย่างไรเพราะฉันไม่เห็นว่าเธรดเหล่านี้แตกต่างกันอย่างไร :) BTW: ลิงก์ของคุณไปยัง Thread Pooling นำฉันไปที่ibm.com/developerworks/library/j-jtp0730.html :)
Andreas Hornig

5
ดูตัวอย่างที่นี่: java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/… มันจะบอกวิธีที่คล่องตัวกว่าในการสร้างและจัดการเธรดพูล ... มันอาจจะดูเหมือน ซับซ้อนกว่าในตอนแรก แต่ก็เหมือนกับเรื่องส่วนใหญ่มันซับซ้อนกว่าเพราะถ้ามันง่ายกว่านั้นคุณก็จะถึงขีด จำกัด เร็วขึ้น
Bill K

62

คุณอาจต้องการดูกรอบงาน java.util.concurrent สำหรับสิ่งนี้ด้วย สิ่งที่ต้องการ:

ExecutorService e = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// Do work using something like either
e.execute(new Runnable() {
        public void run() {
            // do one task
        }
    });

หรือ

    Future<String> future = pool.submit(new Callable<String>() {
        public String call() throws Exception {
            return null;
        }
    });
    future.get();  // Will block till result available

สิ่งนี้ดีกว่าการรับมือกับเธรดพูลของคุณเองเป็นต้น


สวัสดี DaveC อืมฉันไม่เคยรู้มาก่อนดังนั้นฉันจะดูเรื่องนี้ และสามารถปรับขนาดตามแกน cpu ที่มีได้หรือไม่? เพราะฉันไม่เห็นสิ่งนั้นในตัวอย่างสั้น ๆ ของคุณ ขอแสดงความนับถือ Andreas
Andreas Hornig

3
java.util.concurrent สามารถปรับขนาดได้สูง
Kristopher Ives

4
พูลขนาดคงที่พร้อมจำนวนโปรเซสเซอร์ที่มีอยู่มักจะเหมาะสมที่สุดสำหรับกระบวนการที่เชื่อมต่อกับ CPU ตัวอย่างแรกคือสิ่งที่คุณต้องทำ
Peter Lawrey

1
ตามที่ระบุไว้ในความคิดเห็นแรกของคำตอบที่ยอมรับจะดีกว่าถ้าใช้ "โปรเซสเซอร์" ที่รายงานครึ่งหนึ่งด้วยเหตุผลสองประการ: 1. หากคุณมีไฮเปอร์เธรดจำนวนโปรเซสเซอร์ที่แท้จริงคือครึ่งหนึ่งของจำนวนที่รายงาน และ 2. ช่วยให้ระบบประมวลผลส่วนที่เหลือทำงานได้ (OS และโปรแกรมอื่น ๆ )
Matthieu

10

ตัวเลือกที่ 1:

newWorkStealingPoolจากExecutors

public static ExecutorService newWorkStealingPool()

สร้างเธรดพูลที่ขโมยงานโดยใช้โปรเซสเซอร์ที่มีอยู่ทั้งหมดเป็นระดับความขนานของเป้าหมาย

ด้วย API ExecutorServiceนี้คุณไม่ต้องผ่านการจำนวนของแกนไป

การใช้งาน API นี้จากgrepcode

/**
     * Creates a work-stealing thread pool using all
     * {@link Runtime#availableProcessors available processors}
     * as its target parallelism level.
     * @return the newly created thread pool
     * @see #newWorkStealingPool(int)
     * @since 1.8
     */
    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

ทางเลือกที่ 2:

newFixedThreadPool API จากExecutorsหรือother newXXX constructorsซึ่งส่งคืนExecutorService

public static ExecutorService newFixedThreadPool(int nThreads)

แทนที่ nThreads ด้วย Runtime.getRuntime().availableProcessors()

ทางเลือกที่ 3:

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit unit,
                      BlockingQueue<Runnable> workQueue)

ผ่านเป็นพารามิเตอร์Runtime.getRuntime().availableProcessors()maximumPoolSize


8

Doug Lea (ผู้เขียนแพ็คเกจพร้อมกัน) มีเอกสารนี้ซึ่งอาจเกี่ยวข้อง: http://gee.cs.oswego.edu/dl/papers/fj.pdf

เพิ่มเฟรมเวิร์ก Fork Join ใน Java SE 7 ด้านล่างนี้เป็นข้อมูลอ้างอิงเพิ่มเติมบางส่วน:

http://www.ibm.com/developerworks/java/library/j-jtp11137/index.html บทความโดย Brian Goetz

http://www.oracle.com/technetwork/articles/java/fork-join-422606.html


4

วิธีมาตรฐานคือเมธอด Runtime.getRuntime (). availableProcessors () สำหรับซีพียูมาตรฐานส่วนใหญ่คุณจะส่งคืนจำนวนเธรดที่เหมาะสมที่สุด (ซึ่งไม่ใช่จำนวนคอร์ของ CPU จริง) ดังนั้นนี่คือสิ่งที่คุณกำลังมองหา

ตัวอย่าง:

ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

อย่าลืมปิดบริการตัวดำเนินการเช่นนี้ (มิฉะนั้นโปรแกรมของคุณจะไม่ออก):

service.shutdown();

นี่เป็นเพียงสรุปสั้น ๆ เกี่ยวกับวิธีตั้งค่ารหัส MT ตามอนาคต (offtopic สำหรับภาพประกอบ):

CompletionService<YourCallableImplementor> completionService = 
    new ExecutorCompletionService<YourCallableImplementor>(service);
    ArrayList<Future<YourCallableImplementor>> futures = new ArrayList<Future<YourCallableImplementor>>();
    for (String computeMe : elementsToCompute) {
        futures.add(completionService.submit(new YourCallableImplementor(computeMe)));
    }

จากนั้นคุณต้องติดตามจำนวนผลลัพธ์ที่คุณคาดหวังและเรียกดูดังนี้:

try {
  int received = 0;
  while (received < elementsToCompute.size()) {
     Future<YourCallableImplementor> resultFuture = completionService.take(); 
     YourCallableImplementor result = resultFuture.get();
     received++; 
  }
} finally {
  service.shutdown();
}

2
ในที่สุดก็ควรลองปิดการโทร
Christophe Roussy

1
@ChristopheRoussy คุณพูดถูกมากฉันได้แก้ไขตัวอย่างแล้วขอบคุณ!
fl0w

3

บนคลาส Runtime มีวิธีการที่เรียกว่า availableProcessors () คุณสามารถใช้เพื่อหาจำนวน CPU ที่คุณมี เนื่องจากโปรแกรมของคุณถูกผูกไว้กับ CPU คุณอาจต้องการ (มากที่สุด) หนึ่งเธรดต่อ CPU ที่มี


สวัสดี Jason และ Eric (ฉันใช้ความคิดเห็นเดียวสำหรับทั้งสองคำตอบของคุณเพราะโดยพื้นฐานแล้วมันเหมือนกัน) โอเคดีที่จะตรวจสอบ แต่นี่จะเป็นส่วนแรก เมื่อฉันมีจำนวนแกนฉันต้องมีเธรดเป็นตัวแปรเท่ากับจำนวนคอร์นี้ ฉันลองใช้ตัวอย่างนี้ก่อนopenbook.galileodesign.de/javainsel5/… (เยอรมัน!) และใช้เธรดคงที่ แต่ฉันต้องการให้มีการเขียนโปรแกรมแบบเดียวกันโดยใช้ 2 คอร์ในสภาพแวดล้อมแบบดูอัลคอร์และ 4 คอร์ในสภาพแวดล้อมแบบควอดคอร์ ฉันไม่ต้องการเปลี่ยนแปลงด้วยตนเอง เป็นไปได้หรือไม่ ขอบคุณ! :)
Andreas Hornig

@Andreas - ดูการอัปเดตที่ฉันทำในโพสต์ของฉัน ฉันคิดว่าจะช่วยชี้แจงประเด็นได้
JasCav
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.