থ্রেড পুলের চূড়ান্ত লক্ষ্য এবং কাঁটাচামচ / যোগদান একই রকম: দু'জনই উপলব্ধ সিপিইউ শক্তিটিকে সর্বোচ্চ থ্রুপুটের জন্য সর্বোত্তমভাবে ব্যবহার করতে চায়। সর্বাধিক থ্রুটপুটটির অর্থ হ'ল সম্ভব দীর্ঘ সময়কালে যতগুলি কাজ সম্পন্ন করা উচিত। এটা করার দরকার কী? (নিম্নলিখিতগুলির জন্য আমরা ধরে নেব যে গণনার কার্যগুলির কোনও অভাব নেই: 100% সিপিইউ ব্যবহারের জন্য সবসময় যথেষ্ট পরিমাণে থাকে। অতিরিক্ত আমি হাইপার-থ্রেডিংয়ের ক্ষেত্রে কোর বা ভার্চুয়াল কোরগুলির জন্য সমানভাবে "সিপিইউ" ব্যবহার করি)।
- কমপক্ষে সেখানে অনেকগুলি থ্রেড চালানো দরকার যতটা সিপিইউ উপলব্ধ রয়েছে, কারণ কম থ্রেড চালানো একটি কোরকে অব্যবহৃত রাখবে।
- সর্বোচ্চ সিপিইউ রয়েছে তত বেশি থ্রেড চলমান থাকতে হবে, কারণ আরও থ্রেড চালানো তফসিলিকারীর জন্য অতিরিক্ত লোড তৈরি করবে যারা সিপিইউকে বিভিন্ন থ্রেডগুলিতে বরাদ্দ করে যা কিছু সিপিইউ সময়কে আমাদের কম্পিউটেশনাল টাস্কের পরিবর্তে শিডিয়ুলারে যেতে দেয়।
সুতরাং আমরা বুঝতে পেরেছি যে সর্বাধিক থ্রুপুট জন্য আমাদের সিপিইউগুলির তুলনায় একই ধরণের থ্রেড থাকা দরকার। ওরাকলের ঝাপসা উদাহরণে আপনি উভয় উপলব্ধ সিপিইউর সংখ্যার সমান থ্রেডের সংখ্যার সাথে একটি নির্দিষ্ট আকারের থ্রেড পুল নিতে পারেন বা থ্রেড পুল ব্যবহার করতে পারেন। এতে কোনও তফাত হবে না, আপনি ঠিক বলেছেন!
সুতরাং আপনি কখন থ্রেড পুল নিয়ে সমস্যায় পড়বেন? এটি যদি কোনও থ্রেড ব্লক করে , কারণ আপনার থ্রেডটি অন্য কোনও কাজ শেষ হওয়ার অপেক্ষায় রয়েছে। নিম্নলিখিত উদাহরণটি ধরুন:
class AbcAlgorithm implements Runnable {
public void run() {
Future<StepAResult> aFuture = threadPool.submit(new ATask());
StepBResult bResult = stepB();
StepAResult aResult = aFuture.get();
stepC(aResult, bResult);
}
}
আমরা এখানে যা দেখছি তা হল একটি অ্যালগরিদম যা এ, বি এবং সি তিনটি ধাপ নিয়ে গঠিত এবং ক এবং বি একে অপরের থেকে স্বতন্ত্রভাবে সম্পাদন করা যেতে পারে তবে ধাপ সি এর ধাপ A এবং B এর ফলাফলের দরকার যা এই অ্যালগরিদমটি কাজ টাস্ক এ জমা দেয় থ্রেডপুল এবং সরাসরি টাস্ক বি সম্পাদন। এর পরে থ্রেডটি টাস্ক-এ এর পাশাপাশি সম্পন্ন হওয়ার অপেক্ষায় থাকবে এবং সি ধাপে সিটি চালিয়ে যাবে, যদি ক এবং বি একই সময়ে সম্পন্ন হয়, তবে সবকিছু ঠিক আছে। তবে যদি A এর চেয়ে বেশি সময় লাগে? এটি এ কারণ হতে পারে কারণ টাস্ক এ এর প্রকৃতি এটি নির্দেশ করে, তবে এটি এমনও হতে পারে কারণ টাস্কের জন্য থ্রেড না থাকায় শুরুতে উপলব্ধ একটি টাস্কটি অপেক্ষা করা দরকার। (যদি কেবলমাত্র একটি একক সিপিইউ উপলব্ধ থাকে এবং এইভাবে আপনার থ্রেডপুলটিতে একটি মাত্র থ্রেড থাকে এটি এমনকি অচলাবস্থার কারণ হয়ে দাঁড়ায়, তবে আপাতত এটি বিন্দু ছাড়াও রয়েছে)। মুল বক্তব্যটি হ'ল যে থ্রেডটি সবেমাত্র টাস্ক বি কার্যকর করেছেব্লক পুরো থ্রেড । যেহেতু আমাদের একই সংখ্যক থ্রেড সিপিইউ হিসাবে রয়েছে এবং একটি থ্রেড ব্লক করা হয়েছে যার অর্থ একটি সিপিইউ নিষ্ক্রিয় ।
কাঁটাচামচ / যোগদান এই সমস্যার সমাধান করে: কাঁটাচামচ / জোড় ফ্রেমওয়ার্কে আপনি নীচে একই অ্যালগরিদম লিখতে চাই:
class AbcAlgorithm implements Runnable {
public void run() {
ATask aTask = new ATask());
aTask.fork();
StepBResult bResult = stepB();
StepAResult aResult = aTask.join();
stepC(aResult, bResult);
}
}
দেখতে একই রকম, তাই না? তবে খেই যে aTask.join
ব্লক করা হবে না । পরিবর্তে এখানেই যেখানে কাজ-চুরি কার্যকর হয়: থ্রেডটি অন্যান্য কাজগুলির সন্ধান করবে যা অতীতে তৈরি হয়েছিল এবং সেগুলি অবিরত থাকবে। প্রথমে এটি যাচাই করে যে কাজগুলি নিজেই তৈরি করেছে তা প্রক্রিয়াজাতকরণ শুরু করেছে কিনা তা যাচাই করে। সুতরাং যদি এটিকে অন্য থ্রেড দ্বারা এখনও না শুরু করা হয় তবে এটি একটি পরবর্তী কাজ করবে, অন্যথায় এটি অন্যান্য থ্রেডের সারিটি পরীক্ষা করবে এবং তাদের কাজ চুরি করবে। অন্য থ্রেডের এই অন্য কাজটি শেষ হয়ে গেলে এটি এখনই সম্পন্ন হয়েছে কিনা তা পরীক্ষা করে দেখবে। এটি যদি উপরের অ্যালগরিদম কল করতে পারে stepC
। অন্যথায় এটি চুরি করার জন্য আরও একটি কাজ সন্ধান করবে। সুতরাং কাঁটাচামচ / যোগদানের পুলগুলি 100% সিপিইউ ব্যবহার করতে পারে, এমনকি ব্লকিং ক্রিয়াকলাপের পরেও ।
তবে একটি ফাঁদ আছে: কাজের চুরি কেবলমাত্র এস এর join
কলের জন্য সম্ভব ForkJoinTask
। এটি অন্য থ্রেডের জন্য অপেক্ষা করা বা আই / ও ক্রমের জন্য অপেক্ষা করার মতো বাহ্যিক অবরুদ্ধ ক্রিয়াকলাপগুলির জন্য করা যায় না। তাহলে কী, আই / ও শেষ হওয়ার জন্য অপেক্ষা করা একটি সাধারণ কাজ? এই ক্ষেত্রে যদি আমরা কাঁটাচামচ / যোগদানের পুলে একটি অতিরিক্ত থ্রেড যুক্ত করতে পারি যা ব্লকিংয়ের ক্রিয়াটি শেষ হওয়ার সাথে সাথে আবার বন্ধ হয়ে যাবে তবে এটি দ্বিতীয় সেরা কাজ হবে। এবং ForkJoinPool
ক্যান আসলে এটি করতে পারে যদি আমরা ManagedBlocker
গুলি ব্যবহার করি ।
ফিবানচি
ইন RecursiveTask জন্য JavaDoc ব্যবহার ফর্ক / যোগদান ফিবানচি সংখ্যার হিসাব করার একটি উদাহরণ। একটি ক্লাসিক পুনরাবৃত্ত সমাধানের জন্য দেখুন:
public static int fib(int n) {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
যেমন জাভাডোকস হিসাবে ব্যাখ্যা করা হয়েছে এটি ফাইবোনাচি সংখ্যা গণনা করার জন্য এটি একটি সুন্দর ডাম্প উপায়, কারণ এই অ্যালগরিদমে ও (2 ^ n) জটিলতা রয়েছে যখন সহজ উপায়গুলি সম্ভব। তবে এই অ্যালগরিদমটি খুব সহজ এবং সহজে বোঝা যায়, তাই আমরা এটির সাথে আছি। ধরে নেওয়া যাক আমরা কাঁটাচামচ / যোগদানের মাধ্যমে এটির গতি বাড়িয়ে তুলতে চাই। একটি নিষ্পাপ বাস্তবায়ন এইরকম দেখাবে:
class Fibonacci extends RecursiveTask<Long> {
private final long n;
Fibonacci(long n) {
this.n = n;
}
public Long compute() {
if (n <= 1) {
return n;
}
Fibonacci f1 = new Fibonacci(n - 1);
f1.fork();
Fibonacci f2 = new Fibonacci(n - 2);
return f2.compute() + f1.join();
}
}
এই টাস্কটি যে ধাপগুলিতে বিভক্ত হয়েছে সেগুলি খুব ছোট এবং এইভাবে এটি ভয়াবহভাবে সঞ্চালন করবে তবে আপনি দেখতে পাচ্ছেন যে কাঠামোটি সাধারণত কীভাবে খুব ভালভাবে কাজ করে: দুটি সমানকে স্বাধীনভাবে গণনা করা যায়, তবে তারপরে ফাইনালটি তৈরি করার জন্য আমাদের উভয়ের প্রয়োজন need ফলাফল. সুতরাং অর্ধেকটি অন্য থ্রেডে করা হয়। অচলাবস্থা না পেয়ে থ্রেড পুলগুলির সাথে একই কাজ করতে মজা করুন (সম্ভব, তবে প্রায় সহজ নয়)।
কেবলমাত্র সম্পূর্ণতার জন্য: আপনি যদি এই পুনরাবৃত্ত পদ্ধতির ব্যবহার করে ফিবোনাচি নম্বরগুলি প্রকৃতপক্ষে গণনা করতে চান তবে এটি একটি অনুকূলিত সংস্করণ:
class FibonacciBigSubtasks extends RecursiveTask<Long> {
private final long n;
FibonacciBigSubtasks(long n) {
this.n = n;
}
public Long compute() {
return fib(n);
}
private long fib(long n) {
if (n <= 1) {
return 1;
}
if (n > 10 && getSurplusQueuedTaskCount() < 2) {
final FibonacciBigSubtasks f1 = new FibonacciBigSubtasks(n - 1);
final FibonacciBigSubtasks f2 = new FibonacciBigSubtasks(n - 2);
f1.fork();
return f2.compute() + f1.join();
} else {
return fib(n - 1) + fib(n - 2);
}
}
}
এটি সাবটাস্কগুলিকে অনেক ছোট রাখে কারণ এগুলি কেবল যখন n > 10 && getSurplusQueuedTaskCount() < 2
সত্য হয় তখনই বিভক্ত হয়, যার অর্থ এখানে করার জন্য উল্লেখযোগ্যভাবে 100 টিরও বেশি পদ্ধতি কল রয়েছে ( n > 10
) এবং ইতিমধ্যে অপেক্ষা করা খুব বেশি লোকের কাজ নেই ( getSurplusQueuedTaskCount() < 2
)।
আমার কম্পিউটারে (4 টি (8 হাইপার-থ্রেডিং গণনা করার সময়), ইন্টেল (আর) কোর (টিএম) i7-2720QM সিপিইউ @ 2.20GHz) fib(50)
ক্লাসিক পদ্ধতির সাথে seconds৪ সেকেন্ড এবং কাঁটাচামচ / যোগদানের পদ্ধতির সাথে মাত্র 18 সেকেন্ড সময় নেয় তাত্ত্বিকভাবে যতটা সম্ভব সম্ভব না হলেও এটি বেশ লক্ষণীয় লাভ।
সারসংক্ষেপ
- হ্যাঁ, আপনার উদাহরণে কাঁটাচামচ / যোগদানের ক্লাসিক থ্রেড পুলগুলির তুলনায় কোনও সুবিধা নেই।
- ব্লক জড়িত থাকার সময় কাঁটাচামচ / যোগদানের ক্রমশ কর্মক্ষমতা উন্নত করতে পারে
- কাঁটাচামচ / যোগদান কিছু অচলাবস্থার সমস্যাগুলির সমাধান করে