আসুন নতুন ওয়ার্কস্টিলিংপুল পদ্ধতিটি বের করি, যা আমাদের জন্য একটি এক্সিকিউটর সার্ভিস প্রস্তুত করে।

এই থ্রেড পুল বিশেষ. এর আচরণ "চুরি" কাজের ধারণার উপর ভিত্তি করে।

কার্যগুলি সারিবদ্ধ এবং প্রসেসরগুলির মধ্যে বিতরণ করা হয়। কিন্তু যদি একটি প্রসেসর ব্যস্ত থাকে, তবে অন্য একটি ফ্রি প্রসেসর এটি থেকে একটি কাজ চুরি করে এটি কার্যকর করতে পারে। মাল্টি-থ্রেডেড অ্যাপ্লিকেশানগুলিতে দ্বন্দ্ব কমানোর জন্য জাভাতে এই বিন্যাসটি চালু করা হয়েছিল। এটি ফর্ক/জইন ফ্রেমওয়ার্কের উপর নির্মিত ।

কাঁটা/যোগদান

ফর্ক /জইন ফ্রেমওয়ার্কে, টাস্কগুলো পুনরাবৃত্তভাবে পচে যায়, অর্থাৎ সেগুলিকে সাবটাস্কে বিভক্ত করা হয়। তারপরে সাবটাস্কগুলি পৃথকভাবে সম্পাদিত হয় এবং সাবটাস্কগুলির ফলাফলগুলিকে একত্রিত করে মূল টাস্কের ফলাফল তৈরি করা হয়।

ফর্ক পদ্ধতি কিছু থ্রেডে অ্যাসিঙ্ক্রোনাসভাবে একটি কাজ শুরু করে এবং যোগদান পদ্ধতি আপনাকে এই কাজটি শেষ হওয়ার জন্য অপেক্ষা করতে দেয়

নতুন ওয়ার্কস্টিলিংপুল

নতুন ওয়ার্কস্টিলিংপুল পদ্ধতিতে দুটি বাস্তবায়ন রয়েছে:


public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
 
public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

শুরু থেকেই, আমরা নোট করি যে হুডের নীচে আমরা ThreadPoolExecutor কনস্ট্রাক্টরকে কল করছি না। এখানে আমরা ForkJoinPool সত্তার সাথে কাজ করছি । ThreadPoolExecutor এর মত , এটি AbstractExecutorService- এর একটি বাস্তবায়ন ।

আমাদের বেছে নেওয়ার জন্য 2টি পদ্ধতি রয়েছে। প্রথমটিতে, আমরা নিজেরাই নির্দেশ করি যে আমরা কোন স্তরের সমান্তরালতা দেখতে চাই। যদি আমরা এই মানটি নির্দিষ্ট না করি, তাহলে আমাদের পুলের সমান্তরালতা জাভা ভার্চুয়াল মেশিনে উপলব্ধ প্রসেসর কোরের সংখ্যার সমান হবে।

এটি অনুশীলনে কীভাবে কাজ করে তা নির্ধারণ করা বাকি রয়েছে:


Collection<Callable<Void>> tasks = new ArrayList<>();
        ExecutorService executorService = Executors.newWorkStealingPool(10);
 
        for (int i = 0; i < 10; i++) {
            int taskNumber = i;
            Callable<Void> callable = () -> {
                System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
                return null;
            };
            tasks.add(callable);
        }
        executorService.invokeAll(tasks);

আমরা 10টি কাজ তৈরি করি যা তাদের নিজস্ব সমাপ্তির স্থিতি প্রদর্শন করে। এর পরে, আমরা invokeAll পদ্ধতি ব্যবহার করে সমস্ত কাজ চালু করি ।

পুলের 10টি থ্রেডে 10টি কার্য সম্পাদন করার সময় ফলাফল:

ForkJoinPool-1-worker-10 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #9 ForkJoinPool-1-worker-5 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #4 ForkJoinPool-1-worker-8 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #1 ForkJoinPool--এ প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ 1-worker-2 থ্রেড
ForkJoinPool-1-worker-3 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #2
ForkJoinPool-1-worker-4 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #3 ForkJoinPool-1-worker-7 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #6
প্রক্রিয়াকৃত ব্যবহারকারী অনুরোধ #0 ForkJoinPool-1-worker-1 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #5 ForkJoinPool-1-worker-6 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #8 ForkJoinPool-1-worker-9 থ্রেডে

আমরা দেখতে পাই যে সারি তৈরি হওয়ার পরে, থ্রেডগুলি কার্যকর করার জন্য কাজ নেয়। আপনি 10টি থ্রেডের একটি পুলে 20টি কাজ কীভাবে বিতরণ করা হবে তাও পরীক্ষা করতে পারেন।

ForkJoinPool-1-worker-4 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #3 ForkJoinPool-1-worker-8 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #7 ForkJoinPool-1-worker-3 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ
#4 ForkJoinPool--এ প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ
ForkJoinPool-1-worker-2 থ্রেডে 1-worker-5 থ্রেড প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #1 ForkJoinPool
-1-worker-6 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #5 ForkJoinPool-1-worker-9 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #8
প্রক্রিয়াকৃত ব্যবহারকারী অনুরোধ #9 ForkJoinPool-1-worker-10 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #0 ForkJoinPool-1-worker-1 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #6 ForkJoinPool-1-worker-7 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #10 ForkJoinPool-1-তে কর্মী-9 থ্রেড
ForkJoinPool-1-worker-1 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #12 ForkJoinPool-1-worker-8 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #13 ForkJoinPool-1-worker-6 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ
#15 ForkJoinPool-তে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #15 ForkJoinPool- 1-worker-1 থ্রেডে
1-worker-8 থ্রেড প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #14
ForkJoinPool-1-worker-6 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #17 ForkJoinPool-1-worker-7 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #16
প্রক্রিয়াকৃত ব্যবহারকারী ForkJoinPool-1-worker-6 থ্রেডে অনুরোধ #19
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #18 ForkJoinPool-1-worker-1 থ্রেডে

আউটপুট থেকে, আমরা দেখতে পাচ্ছি যে কিছু থ্রেড বেশ কয়েকটি কাজ সম্পূর্ণ করতে পরিচালনা করে ( ForkJoinPool-1-worker-6 4টি কাজ সম্পন্ন করে), যখন কিছু শুধুমাত্র একটি সম্পূর্ণ করে ( ForkJoinPool-1-worker-2 )। কল পদ্ধতি বাস্তবায়নে যদি 1-সেকেন্ড বিলম্ব যোগ করা হয় , ছবি পরিবর্তন হয়।


Callable<Void> callable = () -> {
   System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   TimeUnit.SECONDS.sleep(1);
   return null;
};

পরীক্ষার খাতিরে, অন্য মেশিনে একই কোড চালানো যাক। ফলে আউটপুট:

ForkJoinPool-1-worker-23 থ্রেডে
প্রসেসড ইউজার রিকোয়েস্ট #2 ForkJoinPool-1-worker-31 থ্রেডে
প্রসেসড ইউজার রিকোয়েস্ট #7 ForkJoinPool-1-worker-27 থ্রেডে
প্রসেসড ইউজার রিকোয়েস্ট #5 ForkJoinPool-এ 1-worker-13 থ্রেড
ForkJoinPool-1-worker-19 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #0
ForkJoinPool-1-worker-3 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #8 ForkJoinPool-1-worker-21 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #9
প্রক্রিয়াকৃত ব্যবহারকারী ForkJoinPool-1-worker-17 থ্রেডে
অনুরোধ #6 প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #3 ForkJoinPool-1-worker-9 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #1 ForkJoinPool-1-worker-5 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #12 ForkJoinPool-1-তে worker-23 থ্রেড
ForkJoinPool-1-worker-19 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #15 ForkJoinPool-1-worker-27 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #14 ForkJoinPool-1-worker-3 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #13
ForkJoinPool--এ প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #13
ForkJoinPool-1-worker-31 থ্রেডে 1-worker-13 থ্রেড প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #10 ForkJoinPool
-1-worker-5 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #18 ForkJoinPool-1-worker-9 থ্রেডে প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #16
প্রক্রিয়াকৃত ব্যবহারকারী অনুরোধ #17 ForkJoinPool-1-worker-21 থ্রেডে
প্রক্রিয়াকৃত ব্যবহারকারীর অনুরোধ #19 ForkJoinPool-1-worker-17 থ্রেডে

এই আউটপুটে, এটি উল্লেখযোগ্য যে আমরা পুলের থ্রেডগুলির জন্য "জিজ্ঞাসা করেছি"। আরও কি, কর্মী থ্রেডের নাম এক থেকে দশ পর্যন্ত যায় না, বরং কখনও কখনও দশের থেকেও বেশি হয়। অনন্য নামের দিকে তাকালে, আমরা দেখতে পাচ্ছি যে সত্যিই দশজন কর্মী আছে (3, 5, 9, 13, 17, 19, 21, 23, 27 এবং 31)। এখানে প্রশ্ন করা বেশ যুক্তিসঙ্গত কেন এমন হলো? যখনই আপনি বুঝতে পারবেন না কি হচ্ছে, ডিবাগার ব্যবহার করুন।

এই আমরা কি করব. এর নিক্ষেপ করা যাকনির্বাহক পরিষেবাএকটি ForkJoinPool আপত্তি :


final ForkJoinPool forkJoinPool = (ForkJoinPool) executorService;

আমরা invokeAll পদ্ধতিতে কল করার পরে এই বস্তুটি পরীক্ষা করার জন্য মূল্যায়ন এক্সপ্রেশন অ্যাকশন ব্যবহার করব । এটি করার জন্য, invokeAll পদ্ধতির পরে, যেকোন স্টেটমেন্ট যোগ করুন, যেমন একটি খালি সাউট, এবং এটিতে একটি ব্রেকপয়েন্ট সেট করুন।

আমরা দেখতে পাচ্ছি যে পুলটিতে 10টি থ্রেড রয়েছে, তবে কর্মী থ্রেডের অ্যারের আকার 32। অদ্ভুত, কিন্তু ঠিক আছে। এর খনন রাখা যাক. একটি পুল তৈরি করার সময়, আসুন 32-এর বেশি সমান্তরাল স্তর সেট করার চেষ্টা করি, বলুন 40৷


ExecutorService executorService = Executors.newWorkStealingPool(40);

ডিবাগার, এর তাকান করা যাকforkJoinPool বস্তু আবার.

এখন কর্মী থ্রেডের অ্যারের আকার হল 128। আমরা ধরে নিতে পারি যে এটি JVM-এর অভ্যন্তরীণ অপ্টিমাইজেশনগুলির মধ্যে একটি। আসুন এটি JDK (openjdk-14) এর কোডে খুঁজে বের করার চেষ্টা করি:

ঠিক যেমনটি আমরা সন্দেহ করেছি: কর্মী থ্রেডের অ্যারের আকার সমান্তরাল মানের উপর বিটওয়াইজ ম্যানিপুলেশন সম্পাদন করে গণনা করা হয়। আমাদের এখানে ঠিক কী ঘটছে তা বের করার চেষ্টা করার দরকার নেই। এই ধরনের একটি অপ্টিমাইজেশান বিদ্যমান যে সহজভাবে জানা যথেষ্ট।

আমাদের উদাহরণের আরেকটি আকর্ষণীয় দিক হল invokeAll পদ্ধতির ব্যবহার । এটি লক্ষণীয় যে invokeAll পদ্ধতিটি একটি ফলাফল, বা ফলাফলের একটি তালিকা (আমাদের ক্ষেত্রে, একটি তালিকা<ভবিষ্যত<Void>>) ফেরত দিতে পারে , যেখানে আমরা প্রতিটি কাজের ফলাফল খুঁজে পেতে পারি।


var results = executorService.invokeAll(tasks);
        for (Future<Void> result : results) {
            // Process the task's result
        }

এই বিশেষ ধরনের পরিষেবা এবং থ্রেড পুল একটি অনুমানযোগ্য, বা অন্তত অন্তর্নিহিত, সমঝোতার স্তর সহ কাজে ব্যবহার করা যেতে পারে।