আপডেট এবং পৃথক থ্রেডে রেন্ডার


12

আমি একটি সাধারণ 2 ডি গেম ইঞ্জিন তৈরি করছি এবং আমি স্প্রেটগুলি বিভিন্ন থ্রেডে আপডেট এবং রেন্ডার করতে চাই, এটি কীভাবে করা হয় তা শিখতে।

আমার আপডেট থ্রেডটি সিঙ্ক্রোনাইজ করতে হবে এবং একটি রেন্ডার করতে হবে। বর্তমানে, আমি দুটি পারমাণবিক পতাকা ব্যবহার করি। কর্মপ্রবাহটি দেখতে এমন কিছু দেখাচ্ছে:

Thread 1 -------------------------- Thread 2
Update obj ------------------------ wait for swap
Create queue ---------------------- render the queue
Wait for render ------------------- notify render done
Swap render queues ---------------- notify swap done

এই সেটআপে আমি থ্রেডের এফপিএস আপডেট থ্রেডের এফপিএসের মধ্যে সীমাবদ্ধ করি। আমি sleep()থ্রেডের এফপিএস রেন্ডার এবং আপডেট উভয়কেই সীমাবদ্ধ করতে ব্যবহার করি , সুতরাং দুটি অপেক্ষার ফাংশন বেশি সময় অপেক্ষা করবে না।

সমস্যা হল:

গড় সিপিইউ ব্যবহারের পরিমাণ প্রায় 0.1%। কখনও কখনও এটি 25% (কোয়াড কোর পিসিতে) পর্যন্ত যায়। এর অর্থ একটি থ্রেড অন্যটির জন্য অপেক্ষা করছে কারণ ওয়েট ফাংশনটি একটি পরীক্ষা এবং সেট ফাংশন সহ কিছুক্ষণ লুপ হয় এবং কিছুক্ষণ লুপ আপনার সমস্ত সিপিইউ সংস্থান ব্যবহার করবে।

আমার প্রথম প্রশ্নটি: দুটি থ্রেড সিঙ্ক্রোনাইজ করার অন্য কোনও উপায় আছে কি? আমি লক্ষ্য করেছি যে std::mutex::lockকোনও সংস্থান লক করার অপেক্ষায় থাকাকালীন সিপিইউ ব্যবহার করবেন না যাতে এটি কিছুক্ষণ লুপ হয় না। এটা কিভাবে কাজ করে? আমি ব্যবহার করতে পারছি না std::mutexকারণ আমার এগুলি একটি থ্রেডে লক করা এবং অন্য থ্রেডে আনলক করা দরকার।

অন্য প্রশ্নটি হ'ল; যেহেতু প্রোগ্রামটি সর্বদা 60 এফপিএসে চালিত হয় কেন কখনও কখনও তার সিপিইউ ব্যবহার 25% এ লাফিয়ে যায়, যার অর্থ এই যে দুটি অপেক্ষাগুলির মধ্যে একটির জন্য খুব বেশি অপেক্ষা করা হচ্ছে? (দুটি থ্রেড উভয়ই 60fps এর মধ্যেই সীমাবদ্ধ তাই আদর্শভাবে তাদের অনেকগুলি সংক্রামণের প্রয়োজন হবে না)।

সম্পাদনা: সমস্ত জবাবের জন্য ধন্যবাদ। প্রথমে আমি বলতে চাই যে আমি প্রতিটি ফ্রেম রেন্ডারের জন্য নতুন থ্রেড শুরু করি না। আমি উভয় আপডেট শুরু করি এবং শুরুতে লুপ রেন্ডার করি। আমি মনে করি মাল্টিথ্রেডিং কিছুটা সময় বাঁচাতে পারে: আমার নিম্নলিখিত ফাংশনগুলি রয়েছে: ফাস্টএলজি () এবং আলগ ()। আলগ () উভয়ই আমার আপডেট আপত্তি এবং রেন্ডার আপত্তি এবং ফাস্টালগ () হ'ল "রেন্ডারারে" রেন্ডার সারিটি is একক থ্রেডে:

Alg() //update 
FastAgl() 
Alg() //render

দুটি থ্রেডে:

Alg() //update  while Alg() //render last frame
FastAlg() 

সুতরাং সম্ভবত মাল্টিথ্রেডিং একই সময় সাশ্রয় করতে পারে। (প্রকৃতপক্ষে একটি সাধারণ গণিত অ্যাপ্লিকেশনটিতে এটি ব্যবহার করে, যেখানে আলাগুলি একটি দীর্ঘ অ্যালগরিদম amd ফাস্টালগ দ্রুততর হয়)

আমি জানি যে ঘুম কোনও ভাল ধারণা নয়, যদিও আমার কখনও সমস্যা হয় না। এই আরও ভাল হবে?

While(true) 
{
   If(timer.gettimefromlastcall() >= 1/fps)
   Do_update()
}

তবে এটি এমন একটি অসীম হবে যখন লুপ সমস্ত সিপিইউ ব্যবহার করবে। আমি কি ব্যবহার সীমাবদ্ধ করতে ঘুম (একটি সংখ্যা <15) ব্যবহার করতে পারি? এইভাবে এটি চলবে, উদাহরণস্বরূপ, 100 fps, এবং আপডেট ফাংশনটি প্রতি সেকেন্ডে মাত্র 60 বার কল করা হবে।

দুটি থ্রেড সিঙ্ক্রোনাইজ করতে আমি ক্রিয়েটসিমফোরের সাথে ওয়েটারফোরসিংহলবজেক্টটি ব্যবহার করব যাতে আমি বিভিন্ন থ্রেডে লক এবং আনলক করতে সক্ষম হব (কিছুক্ষণ লুপ ব্যবহার করে হোয়াইটআউট), তাই না?


5
"এই ক্ষেত্রে আমার মাল্টিথ্রেডিং অকেজো বলে বলবেন না, আমি এটি কীভাবে করব তা শিখতে চাই" - সেক্ষেত্রে আপনার জিনিসগুলি যথাযথভাবে শেখা উচিত, এটি হল (ক) ফ্রেমটিকে নিয়ন্ত্রণ করার জন্য ঘুম ব্যবহার করবেন না rare , কখনও কখনও না , এবং (খ) থ্রেড-প্রতি-উপাদান উপাদান ডিজাইন এড়াতে এবং লকস্টেপ চালানো এড়িয়ে চলুন, পরিবর্তে কার্যগুলিতে বিভক্ত হয়ে কাজের সারি থেকে কার্য পরিচালনা করুন।
দামন

1
@ ড্যামন (ক) নিদ্রা (ফ্রেট রেট মেকানিজম) হিসাবে ব্যবহার করা যেতে পারে এবং বাস্তবে এটি বেশ জনপ্রিয়, যদিও এর চেয়ে আরও ভাল বিকল্প আছে বলে আমার একমত হতে হবে। (খ) এখানে ব্যবহারকারী আপডেট দুটি আলাদা করতে এবং দুটি ভিন্ন থ্রেডে রেন্ডার করতে চায়। এটি কোনও গেম ইঞ্জিনের একটি সাধারণ বিচ্ছেদ এবং এতটা "থ্রেড-প্রতি-উপাদান" নয়। এটি সুস্পষ্ট সুবিধা দেয় তবে এটি ভুলভাবে করা থাকলে সমস্যা আনতে পারে।
আলেকজান্দ্রে দেশবিয়েন্স

@ আল্ফস্পিরিট: কিছু "সাধারণ" হওয়ার অর্থ এই নয় যে এটি ভুল নয় । এমনকি বিবিধ টাইমারগুলিতে না গিয়েও, কমপক্ষে একটি জনপ্রিয় ডেস্কটপ অপারেটিং সিস্টেমে ঘুমের কেবলমাত্র গ্রানুলারিটিই হ'ল যথেষ্ট কারণ, যদি প্রতিটি বিদ্যমান ভোক্তা সিস্টেমে এটির অবিশ্বাস্যতা-প্রতি নকশা থাকে । বর্ণিত হিসাবে দুটি আলাদা আলাদা আলাদা আলাদা আলাদা করে আপডেট করা এবং রেন্ডার করা কেন বুদ্ধিমানের কারণ এবং এটির মূল্য তার চেয়ে বেশি সমস্যার কারণ হয়ে দাঁড়ায় too এটি কীভাবে হয়েছে তা শিখতে ওপির লক্ষ্যটি বর্ণিত হয়েছে , এটি সঠিকভাবে কীভাবে করা হয়েছে তা শিখতে হবে । চারপাশে আধুনিক এমটি ইঞ্জিন ডিজাইনে প্রচুর নিবন্ধ।
দামন

@ ড্যামন যখন আমি বলেছিলাম যে এটি জনপ্রিয় বা সাধারণ, তখন আমি এটি সঠিক বলে নিই না। আমি কেবল বোঝাতে চেয়েছিলাম এটি অনেক লোক ব্যবহার করেছিল। "... যদিও আমাকে একমত হতে হবে যে আরও অনেক ভাল বিকল্প রয়েছে" এর অর্থ হ'ল সময়কে সুসংগত করার পক্ষে খুব ভাল উপায় নয়। ভুল জন্য দুঃখিত.
আলেকজান্দ্রে দেশবিয়েন্স

@ অ্যাল্ফস্পিরিট: কোনও উদ্বেগ নেই :-) বিশ্বগুলি এমন অনেক কিছুতে পূর্ণ রয়েছে যা অনেক লোকই করে (এবং সবসময় কোনও ভাল কারণেই নয়) তবে যখন কেউ শেখা শুরু করে, তখনও একজনকে অবশ্যই সবচেয়ে স্পষ্টতই ভুল এড়ানোর চেষ্টা করা উচিত।
দামন

উত্তর:


25

স্প্রাইটস সহ একটি সাধারণ 2 ডি ইঞ্জিনের জন্য, একক থ্রেডযুক্ত পদ্ধতির পুরোপুরি ভাল। তবে যেহেতু আপনি মাল্টিথ্রেডিং কীভাবে করবেন তা শিখতে চান, আপনার এটি সঠিকভাবে করা শিখতে হবে।

করো না

  • বেশ কয়েকটি থ্রেডের সাথে একক থ্রেডযুক্ত আচরণ প্রয়োগ করে, কম বেশি লক-স্টেপ চালিত এমন 2 টি থ্রেড ব্যবহার করুন। এটিতে সমান্তরালতার একই স্তরের (শূন্য) রয়েছে তবে প্রসঙ্গের সুইচ এবং সিঙ্ক্রোনাইজেশনের জন্য ওভারহেড যুক্ত করে। এছাড়াও, যুক্তি আঁকাবাঁকা করা শক্ত।
  • sleepফ্রেম হার নিয়ন্ত্রণ করতে ব্যবহার করুন । কখনও। যদি কেউ আপনাকে বলে, তাদের আঘাত করুন।
    প্রথমত, সমস্ত মনিটর 60Hz এ চালায় না। দ্বিতীয়ত, একই সময়ে চলমান পাশাপাশি একই হারে টিক দেওয়া দুই টাইমার সর্বশেষে সিঙ্ক থেকে বেরিয়ে আসবে (একই উচ্চতা থেকে একটি টেবিলে দুটি পিংপং বল ফেলে, এবং শোনো)। তৃতীয়ত, sleepহয় নকশা তন্ন তন্ন নির্ভুল কিংবা নির্ভরযোগ্য। গ্রানুলারিটি 15.6 মিমি হিসাবে খারাপ হতে পারে (আসলে উইন্ডোজ [1] এ ডিফল্ট ), এবং একটি ফ্রেম 60fps এ কেবল 16.6ms হয় যা অন্য কিছুর জন্য কেবল 1 মিমি রেখে দেয়।
    এছাড়াও, 15.6 এর একাধিক হতে 16.6 পাওয়া শক্ত ... এছাড়াও, sleepকেবল 30 বা 50 বা 100 এমএস বা আরও দীর্ঘ সময়ের পরে ফিরে আসার (এবং কখনও কখনও আসবে!) অনুমতি দেওয়া হয়।
  • std::mutexঅন্য থ্রেডকে অবহিত করতে ব্যবহার করুন । এটি এর জন্য নয়।
  • ধরে নিন যে টাস্ক ম্যানেজারটি যা চলছে তা আপনাকে বলতে ভাল, বিশেষত "25% সিপিইউ" এর মতো একটি নম্বর থেকে বিচার করে, যা আপনার কোডে বা ইউজারমোড ড্রাইভারের মধ্যে বা অন্য কোথাও ব্যয় করতে পারে।
  • উচ্চ স্তরের উপাদানগুলির জন্য একটি থ্রেড রাখুন (অবশ্যই কিছু ব্যতিক্রম আছে)।
  • প্রতিটি কাজ হিসাবে "এলোমেলো সময়ে" থ্রেড তৈরি করুন। থ্রেডগুলি তৈরি করা আশ্চর্যজনকভাবে ব্যয়বহুল হতে পারে এবং আপনি তাদের যা বলেছেন তা তাত্ক্ষণিকভাবে করার আগে তারা আশ্চর্যজনকভাবে দীর্ঘ সময় নিতে পারে (বিশেষত যদি আপনার প্রচুর ডিএলএল বোঝাই থাকে!)।

ডু

  • কিছু চালানো আছে multithreading ব্যবহার করুন অ্যাসিঙ্ক্রোনাস যতটা আপনি পারেন হিসাবে। গতি থ্রেডিংয়ের মূল ধারণা নয়, তবে সমান্তরালভাবে জিনিসগুলি করা (তাই তারা পুরোপুরি আরও বেশি সময় নিলেও, সমস্তটির যোগফল এখনও কম)।
  • ফ্রেম হার সীমাবদ্ধ করতে উল্লম্ব সিঙ্ক ব্যবহার করুন। এটি করার একমাত্র সঠিক (এবং ব্যর্থতা) উপায়। যদি ব্যবহারকারী আপনাকে ডিসপ্লে ড্রাইভারের নিয়ন্ত্রণ প্যানেলে ("জোর করে বন্ধ") ওভাররাইড করে, তবে তাই হয়ে যান। সর্বোপরি এটি তার কম্পিউটার, আপনার নয়।
  • নিয়মিত বিরতিতে যদি আপনার কোনও কিছু "টিক" লাগানোর দরকার হয় তবে টাইমার ব্যবহার করুনsleep[২] এর তুলনায় টাইমারের আরও ভাল নির্ভুলতা এবং নির্ভরযোগ্যতা থাকার সুবিধা রয়েছে । এছাড়াও, একটি পুনরাবৃত্ত টাইমার সঠিকভাবে সময়ের জন্য অ্যাকাউন্টগুলি (এর মধ্যবর্তী সময় সহ) যখন 16.6 মিমি (বা 16.6 মাইনাস মাপা_টাইম_মিলাক্সিত) জন্য ঘুমায় না।
  • নির্দিষ্ট পদক্ষেপে সংখ্যার একীকরণের সাথে জড়িত পদার্থবিজ্ঞানের সিমুলেশনগুলি চালনা করুন (বা আপনার সমীকরণগুলি বিস্ফোরিত হবে!), ধাপগুলির মধ্যে গ্রাফিকগুলিকে বিচ্ছিন্ন করুন (এটি পৃথক প্রতি-উপাদান-থ্রেডের জন্য একটি অজুহাত হতে পারে, তবে এটি ছাড়াও করা যেতে পারে)।
  • std::mutexএকবারে কেবলমাত্র একটি থ্রেড অ্যাক্সেসে ব্যবহার করতে ("পারস্পরিক বাদ দিয়ে") ব্যবহার করতে এবং এর অদ্ভুত শব্দার্থবিজ্ঞানের সাথে সম্মতি জানাতে ব্যবহার করুন std::condition_variable
  • সম্পদের জন্য থ্রেড প্রতিযোগিতা থাকা এড়িয়ে চলুন। প্রয়োজনীয় হিসাবে অল্প লক করুন (তবে কোনওটিই কম নয়!) এবং লকগুলি যতক্ষণ না একেবারে প্রয়োজন ততক্ষণ ধরে রাখুন।
  • থ্রেডগুলির মধ্যে কেবল পঠনযোগ্য ডেটা ভাগ করুন (কোনও ক্যাশে সমস্যা নেই, এবং কোনও লকিং প্রয়োজনীয় নয়) তবে একই সাথে ডেটা সংশোধন করবেন না (সিঙ্কের প্রয়োজন এবং ক্যাশেটি মেরে ফেলে)। এর মধ্যে ডেটা সংশোধন করা রয়েছে যা কাছাকাছি অবস্থিত অন্য কেউ পড়তে পারে।
  • std::condition_variableকিছু শর্ত সত্য না হওয়া অবধি অন্য থ্রেড ব্লক করতে ব্যবহার করুন । std::condition_variableঅতিরিক্ত মুউটেক্স সহ শব্দার্থবিজ্ঞান স্বীকারোচিতভাবে বেশ অদ্ভুত এবং বাঁকানো (বেশিরভাগ historicতিহাসিক কারণে পসিক্স থ্রেড থেকে উত্তরাধিকার সূত্রে প্রাপ্ত), তবে একটি শর্ত পরিবর্তনশীল আপনার যা চান তা ব্যবহার করার জন্য সঠিক আদিম।
    যদি আপনি std::condition_variableএটিতে স্বাচ্ছন্দ্য বোধ করতে খুব অদ্ভুত মনে করেন তবে আপনি সহজেই উইন্ডোজ ইভেন্টটি ব্যবহার করতে পারেন (কিছুটা ধীরে ধীরে) এর পরিবর্তে বা আপনি যদি সাহসী হন তবে এনটিকিয়েডেভেন্টস (ভীতিকর নিম্ন স্তরের স্টাফগুলিতে জড়িত) এর চারপাশে আপনার নিজস্ব সাধারণ ইভেন্টটি তৈরি করুন। আপনি যেমন ডাইরেক্টএক্স ব্যবহার করেন, আপনি ইতিমধ্যে যেভাবেই উইন্ডোজকে আবদ্ধ হন, সুতরাং বহনযোগ্যতা হ্রাস কোনও বিগী হওয়া উচিত নয়।
  • স্থির আকারের কর্মী থ্রেড পুল দ্বারা চালিত যুক্তিসঙ্গত আকারের কাজগুলিতে বিরতি দিন (কোর প্রতি একের বেশি নয়, হাইপারথ্রেড কোরগুলি গণনা করছেন না)। সমাপ্তি কার্যগুলিকে নির্ভরশীল কর্মগুলি মুক্ত করুন (বিনামূল্যে, স্বয়ংক্রিয়ভাবে সিঙ্ক্রোনাইজেশন)। প্রতিটি যাতে কমপক্ষে কয়েক শতাধিক তুচ্ছ ক্রিয়াকলাপ (বা ডিস্ক রিডের মতো একটি দৈর্ঘ্য ব্লকিং অপারেশন) রয়েছে এমন কাজগুলি করুন। ক্যাশে-স্থির অ্যাক্সেস পছন্দ করুন।
  • প্রোগ্রাম শুরুতে সমস্ত থ্রেড তৈরি করুন।
  • ওএস বা গ্রাফিক্স এপিআই আরও ভাল / অতিরিক্ত সমান্তরালতার জন্য অফার করে এমন অ্যাসিঙ্ক্রোনাস ফাংশনগুলির সুবিধা গ্রহণ করুন, কেবলমাত্র প্রোগ্রামের স্তরে নয়, হার্ডওয়্যারেও (পিসিআই জিপিইউ সমান্তরালতা, ডিস্ক ডিএমএ ইত্যাদি ভাবেন)।
  • 10,000 অন্যান্য বিষয় যা আমি উল্লেখ করতে ভুলে গেছি।


[1] হ্যাঁ, আপনি শিডিয়ুলারের হার 1 মিমি তে সেট করতে পারেন, তবে এটি অনেকটা প্রসঙ্গের সুইচ তৈরি করে এবং আরও অনেক বেশি শক্তি খরচ করে (এমন একটি দেশে যেখানে আরও বেশি সংখ্যক ডিভাইস মোবাইল ডিভাইস হয়) তা নির্ধারণ করতে পারে। এটি এখনও কোনও সমাধান নয় কারণ এটি এখনও ঘুমকে আরও নির্ভরযোগ্য করে তোলে না।
[২] একটি টাইমার থ্রেডের অগ্রাধিকার বাড়িয়ে তুলবে, যা এটি অন্য সম-অগ্রাধিকারের থ্রেডকে মাঝ-কোয়ান্টামকে বাধাগ্রস্ত করতে এবং প্রথমে নির্ধারিত করা হবে, এটি একটি আধাসিটি-আরটি আচরণ। এটি অবশ্যই সত্য আরটি নয়, তবে এটি খুব কাছাকাছি আসে comes নিদ্রা থেকে জেগে ওঠার অর্থ কেবল যে কোনও সময় থ্রেড নির্ধারিত হওয়ার জন্য প্রস্তুত হয়ে যায়, যখনই তা হতে পারে।


আপনি দয়া করে ব্যাখ্যা করতে পারেন কেন আপনার "উচ্চ স্তরের উপাদানগুলির জন্য একটি থ্রেড থাকা উচিত নয়"? আপনি কী বোঝাতে চেয়েছেন যে দুটি পৃথক থ্রেডে ফিজিক্স এবং অডিও মিশ্রিত হওয়া উচিত নয়? আমি তা না করার কোনও কারণ দেখছি না।
এলভিস স্ট্রেজডিনস

3

আপডেটের এফপিএস সীমাবদ্ধ করে আপনি 60 টির মধ্যে রেন্ডার করে আপনি কী অর্জন করতে চান তা আমি নিশ্চিত নই you আপনি যদি তাদের একই মানের সীমাবদ্ধ করেন, আপনি কেবল তাদের একই থ্রেডে রাখতে পারতেন।

আপডেট এবং রেন্ডারকে আলাদা আলাদা থ্রেডে আলাদা করার সময় লক্ষ্যটি হল উভয়কে একে অপরের থেকে "প্রায়" স্বতন্ত্র রাখা, যাতে জিপিইউ 500 টি এফপিএস উপস্থাপন করতে পারে এবং আপডেট লজিক এখনও 60 এফপিএসে যায়। আপনি এটি করে খুব বেশি পারফরম্যান্স লাভ করতে পারেন না।

তবে আপনি বলেছিলেন যে আপনি এটি কীভাবে কাজ করে তা কেবল জানতে চেয়েছিলেন এবং এটি ঠিক আছে। সি ++ তে, একটি মিউটেক্স একটি বিশেষ অবজেক্ট যা অন্যান্য থ্রেডের জন্য নির্দিষ্ট সংস্থানগুলিতে অ্যাক্সেস লক করতে ব্যবহৃত হয়। অন্য কথায়, আপনি কেবল একটি থ্রেড দ্বারা বুদ্ধিমান ডেটা অ্যাক্সেসযোগ্য করার জন্য একটি মিটেক্স ব্যবহার করেন। এটি করার জন্য এটি বেশ সহজ:

std::mutex mutex;
mutex.lock();
// Do sensible stuff here...
mutex.unlock();

সূত্র: http://en.cppreferences.com/w/cpp/thread/mutex

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

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


এবং ব্লকটি কীভাবে কাজ করবে?
লিউকা

যখন দ্বিতীয় থ্রেডটি লকটি কল করবে (), এটি ধৈর্য সহকারে প্রথম থ্রেডটিকে মিউটেক্স আনলক করার জন্য অপেক্ষা করবে এবং পরবর্তী লাইনে এটি চালিয়ে যাবে (উদাহরণস্বরূপ, বুদ্ধিমান জিনিস)। সম্পাদনা: দ্বিতীয় থ্রেডটি তখন নিজের জন্য মিটেক্সটিকে লক করবে।
আলেকজান্দ্রে দেশবিয়েন্স 13


1
ব্যবহার করুন std::lock_guardবা অনুরূপ, .lock()/ নয় .unlock()। আরআইআই কেবল মেমরি পরিচালনার জন্য নয়!
খ্রিস্ট
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.