সি ++ 11 এ কিভাবে পূর্ণসংখ্যার থ্রেড আইডি পাবেন


88

সি ++ 11 এর বর্তমান থ্রেড আইডি পাওয়ার সম্ভাবনা রয়েছে তবে এটি পূর্ণসংখ্যার টাইপের ক্ষেত্রে কাস্টযোগ্য নয়:

cout<<std::this_thread::get_id()<<endl;

আউটপুট: 139918771783456

cout<<(uint64_t)std::this_thread::get_id()<<endl;

ত্রুটি: 'স্ট্যান্ড :: থ্রেড :: আইডি' টাইপ থেকে 'uint64_t' টাইপ একই ধরণের অবৈধ কাস্ট: টাইপ 'স্ট্যান্ড :: থ্রেড :: আইডি' টাইপ থেকে 'uint32_t' টাইপ করুন

আমি পূর্ণসংখ্যার থ্রেড আইডি পেতে পয়েন্টার কাস্টিং করতে চাই না। এটি করার জন্য কোনও যুক্তিসঙ্গত উপায় (মানক কারণ আমি এটি পোর্টেবল হতে চাই)?


13
এটির জন্য পূর্ণসংখ্যার কী দরকার? এটিতে কোনও ধরণের পাটিগণিত না করা গ্যারান্টিযুক্ত এবং এটি প্রক্রিয়াটির প্রেক্ষাপটের বাইরে অর্থবহ নয়, সুতরাং এটি ডিবাগিং ছাড়া অন্য সিরিয়ালিয়াল করার দরকার নেই (যা operator<<মনে হয় এটি ঠিকঠাক পরিচালনা করে)।
হামাখোলম

4
এর মতো কিছু: 1024cores.net/home/lock-free-algorithms/false-sharing---false তবে N = MAX_THREAD_COUNT এর পরিবর্তে আমার কাছে N = 128 এর মতো কিছু থাকবে এবং থ্রেড_আইডি% N
NoSenseEtAl

9
আপনি যদি সত্যিই এটি পোর্টেবল হতে চান তবে আপনার পূর্বে thread::idপূর্ণসংখ্যা হিসাবে প্রদর্শিত না হওয়ার সম্ভাবনার জন্য আপনাকে প্রস্তুত থাকতে হবে। আপনার লিঙ্ক করা পৃষ্ঠাটি থ্রেড আইডি দ্বারা সূচিযুক্ত একটি অ্যারে ব্যবহার করে। আপনি কি map<thread::id, int>পরিবর্তে ব্যবহার বিবেচনা করেছেন ? তারপরে আপনি idকোনও রূপান্তর না করে ক্লাসের জন্য ইতিমধ্যে সংজ্ঞায়িত অপারেটরগুলি ব্যবহার করতে পারেন use স্ট্যান্ডার্ডটিও সংজ্ঞায়িত করে hash<thread::id>, যাতে আপনি অর্ডারযুক্ত পাত্রেও ব্যবহার করতে পারেন।
রব কেনেডি

4
@ এই মানচিত্রে রবটিকে নিঃশব্দ করা প্রয়োজন :(
NoSenseEtAl

4
@ সুইসফ্র্যাঙ্ক বা আমাকে সিএইচএফ বলতে হবে: পিআই এখনও রয়েছি, তবে আমি মনে করি গ্রহণযোগ্য উত্তরটি আমার পক্ষে ঠিক আছে, কোনও প্রোগ্রামের সময়কালের জন্য ভেরিয়েবল আইডি মানগুলি অনন্য কিনা তা নিশ্চিত করা আমার পক্ষে up
NoSenseEtAl

উত্তর:


35

পোর্টেবল সমাধান হ'ল থ্রেডে আপনার নিজস্ব উত্পাদিত আইডি পাস করা।

int id = 0;
for(auto& work_item : all_work) {
    std::async(std::launch::async, [id,&work_item]{ work_item(id); });
    ++id;
}

std::thread::idপ্রকারটি কেবল তুলনা করার জন্য ব্যবহার করা হয়, পাটিগণিতের জন্য নয় (যেমন এটি ক্যানের উপরে যেমন বলা হয়েছে: একটি সনাক্তকারী )) এমনকি তার টেক্সট উপস্থাপনা দ্বারা উত্পাদিত operator<<হয় অনির্দিষ্ট তাই আপনি এটি একটি সংখ্যা প্রতিনিধিত্ব হচ্ছে উপর নির্ভর করে না করতে পারেন।

আপনি std::thread::idনিজের আইডিতে মানচিত্রের মানচিত্র ব্যবহার করতে পারেন এবং সরাসরি আইডি পাস করার পরিবর্তে এই মানচিত্রটি (যথাযথ সিঙ্ক্রোনাইজেশন সহ) থ্রেডগুলির মধ্যে ভাগ করে নিতে পারেন।


4
আহা! কিন্তু হয় একটি টেক্সট উপস্থাপনা! এটি তাদের পক্ষে দৃশ্যত তাদের মধ্যে পার্থক্য খুঁজে পাওয়ার পক্ষে যথেষ্ট, তাই না?
জুনি

এখানে উল্লিখিত থ্রেড :: আইডি (বা এটি_প্রেম :: get_id ()) সমাধানটি সেরা, কারণ এটি প্রোগ্রামার-নির্দিষ্ট নয়। স্ট্রিং বা পূর্ণসংখ্যার উপস্থাপনা পেতে মাইকের স্ট্রিংস্ট্রিমের উত্তরটি দেখুন।
অ্যান্ড্রু

@ অ্যান্ড্রু আমি উত্তরে সম্বোধন করেছিলাম: "এমনকি এটির পাঠ্য উপস্থাপনা অপারেটর দ্বারা উত্পাদিত << অনির্ধারিত, সুতরাং আপনি এটি কোনও সংখ্যার প্রতিনিধিত্ব হওয়ার কারণে নির্ভর করতে পারবেন না"। মনে হয় "সেরা" শব্দের ছায়াময় সংজ্ঞাটি হাতে রয়েছে।
আর মার্টিনহো ফার্নান্দেস

"সেরা" স্ট্রিং উপস্থাপনার সাথে সম্পর্কিত ছিল না।
অ্যান্ড্রু

4
এছাড়াও, আমি মাত্র আমার নিজের জন্য 10,000,000 পুনরাবৃত্তির সাথে একটি বেঞ্চমার্ক করেছি এবং এটি_প্রেম :: get_id () দুষ্টু দ্রুত: পেস্টবিন. com/eLa3rKQE ডিবাগ মোডে কল প্রতি 0.0000002543827 সেকেন্ড লাগে এবং রিলিজটি আমার প্রতি কলে 0.00000003652367 সেকেন্ড সময় নেয়। (ইন্টেল আই 5 2.60 গিগাহার্টজ)
অ্যান্ড্রু

89

আপনার শুধু করা দরকার

std::hash<std::thread::id>{}(std::this_thread::get_id())

পেতে একটি size_t

সিপ্রেফারেন্স থেকে :

শ্রেণীর std::hashজন্য টেম্পলেট বিশেষায়নের std::thread::idসাহায্যে ব্যবহারকারীরা থ্রেডের শনাক্তকারীদের হ্যাশগুলি পেতে পারবেন।


35
আমি মনে করি এটি হতে হবে std::hash<std::thread::id>()(std::this_thread::get_id()), তাই না?
ব্যারি

12
হ্যাশ অনন্য গ্যারান্টিযুক্ত করা হবে? সম্ভবত না, অনন্য থ্রেড শনাক্তকারী হিসাবে এর ব্যবহারকে পরাস্ত করে।
মাইকেল গোল্ডস্টেইন

4
প্রদত্ত উদাহরণটি কমপক্ষে ঝনঝন 3.4 এবং libstdc ++ 4.8 এর সাথে কাজ করে না। ব্যারি সংস্কার কাজ করে না।
আর্টো বেনডিকেন

4
উত্তরের জন্য 888 ধন্যবাদ। এমএস সংকলকটিতে থ্রেড রয়েছে: আইডি :: হ্যাশ () তবে ব্যারি কোডটি মান মাপসই। হ্যাশ সংঘর্ষ করতে পারে। এটি এখনও থ্রেড প্রতি একটি হ্যাশ (আশা করি 0 এর কাছাকাছি একটি সংঘর্ষের সম্ভাবনা সহ) লাভজনক
a.lasram

4
এমএসভিসি আসলে এই ক্ষেত্রে একটি হ্যাশড থ্রেড আইডি প্রদান করে। আপনি পাশাপাশি নিজের
তৈরিও করতে পারেন

26

অন্য আইডি (ধারণা? ^^) স্ট্রিং স্ট্রিম ব্যবহার করা হবে:

std::stringstream ss;
ss << std::this_thread::get_id();
uint64_t id = std::stoull(ss.str());

এবং যদি জিনিসগুলি ভুল হয়ে যায় তবে আপনি যদি ব্যতিক্রম চান না তবে ক্যাপচার চেষ্টা করুন ...


4
ভাল উত্তর. এটি সাধারণভাবে উদ্দেশ্যটি পরিবেশন করবে।
iammilind

4
এটি বহনযোগ্য নয়, কারণ std::thread::idঅক্ষর হিসাবে প্রিন্টগুলি যে কোনও পূর্ণসংখ্যা তৈরি করে ঠিক তেমনই কোনও গ্যারান্টি নেই যে থ্রেড আইডিটি অভ্যন্তরীণভাবে কোনও পূর্ণসংখ্যার দ্বারা প্রতিনিধিত্ব করে।
blubberdiblub

4
@ নিকস যখনই কোনও বাস্তবায়ন পছন্দ করে যে কোনও পূর্ণসংখ্যা অপর্যাপ্ত। বা যখনই এটি অন্য কোনও কারণে এটি অনুচিত বলে মনে করে। এখানে মুল বক্তব্যটি হ'ল স্পেসিফিকেশন যখন এটি পূর্ণসংখ্যা হিসাবে নির্দিষ্ট করে না (এবং এটি না করে তবে এটির আরও কিছু বিমূর্ত গ্যারান্টি রয়েছে), আপনি কোনও বাস্তবায়নের ক্ষেত্রে এটি পূর্ণসংখ্যার উপর নির্ভর করতে পারবেন না এবং করা উচিত নয়। std::thread::idকিছু সংখ্যার বদলে কেবল টাইপ হিসাবে ব্যবহার করুন , এটি এর জন্য বিদ্যমান। এবং সংখ্যাটির সংখ্যা হিসাবে এটির স্ট্রিং প্রতিনিধিত্বটির পুনরায় ব্যাখ্যা করবেন না। এটি অস্বচ্ছ হিসাবে বা ডিবাগিং / লগিং আউটপুট হিসাবে বিবেচনা করুন।
blubberdiblub

6

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

উত্তরোত্তর জন্য: pthread_self()একটি প্রদান করে pid_tএবং পিক্সিক্স হয়। এটি পোর্টেবলের কিছু সংজ্ঞার জন্য বহনযোগ্য।

gettid(), প্রায় অবশ্যই পোর্টেবল নয়, তবে এটি একটি জিডিবি বান্ধব মান প্রদান করে।


pthread_self()প্রকৃতপক্ষে একটি দেয় pthread_t, যা অস্বচ্ছ হয় (এর বিপরীতে pid_t(ফিরে আসে gettid()) যা প্ল্যাটফর্ম-নির্দিষ্ট, আপাতদৃষ্টিতে একটি পূর্ণসংখ্যা হয়, অন্তত)। তবে প্রথম বিটের জন্য +1, এটি আমার সমস্যার সমাধান!
ক্যামেরন

4

আমি জানি না এটি কত দ্রুত, তবে এটিই আমি সমাধান করতে পেরেছি:

const size_t N_MUTEXES=128;//UINT_MAX,not 128  for answer to my original question
hash<std::thread::id> h;
cout<<h(std::this_thread::get_id())%N_MUTEXES<<endl;

আবার আমি ভাবতে শুরু করি যে কাঠামোর দিকে একটি পয়েন্টার পাওয়া এবং এটি স্বাক্ষরবিহীন ইনট বা uint64_t এ ingালাই করা উত্তর ... সম্পাদনা:

uint64_t get_thread_id()
{
    static_assert(sizeof(std::thread::id)==sizeof(uint64_t),"this function only works if size of thead::id is equal to the size of uint_64");
    auto id=std::this_thread::get_id();
    uint64_t* ptr=(uint64_t*) &id;
    return (*ptr);
}
int main()
{
    cout<<std::this_thread::get_id()<<"  "<<get_thread_id()<<endl;
}

নরকীয় সমস্যা রোধে স্ট্যাটিক_সেট্রেট :) এই জাতীয় বাগের শিকারের তুলনায় পুনর্লিখন সহজ। :)


4
আপনার কোনও গ্যারান্টি নেই যে আপনি hashফাংশনটির সাথে সদৃশ মান পাবেন না , যদি আপনি% এটি করেন তবে খুব কম
আর মার্টিনহো ফার্নান্দেস

4
আপনি যে গ্যারান্টি দিয়ে পেতে পারেন না std::this_thread::get_id()! তবে আপনার সম্ভবত এটির দরকার নেই। একে অপরের সাথে ভাগ করে নেওয়ার জন্য কয়েকটি থ্রেড প্রতিটি থ্রেডের সাথে ভাগ করে নেওয়া প্রতিটি থ্রেডের মতো বিশাল সমস্যা তৈরি করে না। এরকম const size_t N_COUNTERS = 128; struct Counter { std::atomic<int> counter; char pad[CACHE_LINE_SIZE - sizeof(atomic<int>); } counters[N_COUNTERS];কিছু সম্ভবত ঠিক আছে। (খুব লাইটওয়েট সিঙ্ক্রোনাইজেশনের জন্য একটি পারমাণবিক বা স্পিনলক))
স্কট ল্যাম্ব

@ আর। মার্টিনহো ফার্নান্দেসের মতো আমি বলেছিলাম যে আমি ইনট ভ্যালুতে আগ্রহী তাই আমি এটি%% করতে পারি, সংঘর্ষগুলি ঠিক যদি তারা বিরল হয় তবে স্কট যা বলেছিল তা ঠিক আছে।
NoSenseEtAl

4
আমি আসলে এটি চেষ্টা করেছিলাম এবং আমি সম্পূর্ণ ভুল ছিলাম - এর atomic<int>পরিবর্তে ব্যবহার করা intনাটকীয় মন্দা এমনকি কোনও বিতর্ক ছাড়াই down
স্কট ল্যাম্ব

4
আপনি স্ট্যাটিক_সেসার্টটি এই আদর্শের সাথে সংযুক্ত করতে পারেন / কিউ 7 এনএইচ 4 (এটির পরিবর্তে যদি আপনি চান তবে একটি সঠিক আকারের প্রয়োজনীয়তা প্রয়োগ করতে সহজেই সক্রিয়ভাবে নজর দেওয়া যায়) এটি আরও বহনযোগ্যভাবে কাজ করতে পারে (উদাহরণস্বরূপ, আদর্শের কীভাবে 32-বিট থ্রেড আইডি রয়েছে) ।
আর মার্টিনহো ফার্নান্দেস

4

thread::native_handle()প্রত্যাবর্তন thread::native_handle_type, যা একটি টাইপডেফlong unsigned int

থ্রেডটি যদি ডিফল্ট নির্মিত হয়, নেটিটিহ্যান্ডল () 0 প্রদান করে it যদি এটির সাথে কোনও ওএস থ্রেড সংযুক্ত থাকে, তবে ফেরতের মানটি শূন্য নয় (এটি পসিক্সে pthread_t হয়)।


এটি কোথায় std::thread::native_handle_typeটাইপডেফ নির্দিষ্ট করা হয়েছে long unsigned? 30.3.1 / 1 এ আমরা কেবল দেখতে পাচ্ছিtypedef implementation-defined native_handle_type; // See 30.2.3
রুসলান

প্রকারটি আবিষ্কারের একটি বোবা তবে সহজ উপায় হ'ল থ্রেড :: নেটিভ_হ্যান্ডল () উদাহরণস্বরূপ uint8_t নির্ধারিত করে ইচ্ছাকৃত সংকলন ত্রুটি তৈরি করা। তারপরে সংকলকটি টাইপ অমিল সম্পর্কে অভিযোগ করবে এবং টাইপটি কী তা আপনাকে জানাবে।
আলেক্সি পোলোনস্কি

4
ওয়েল এটি অ-বহনযোগ্য যেহেতু এটি নির্দিষ্ট প্রয়োগের উপর নির্ভর করে।
রুসলান

ঠিক আছে, অন্তত অন্তর্নিহিত প্রয়োগটি যদি পসিক্স অ্যাকথার ব্যবহার করে তবে মনে হয় যে নেটিটি_হ্যান্ডেল () অবশ্যই একটি প্যাথড_টি হবে। এখন, pthread_t একটি পয়েন্টার টাইপ (টাইপডেফ স্ট্রাক pthread * pthread_t)। সুতরাং, এটি বোঝা যায় যে স্টাড :: থ্রেড :: নেটিভ_হ্যান্ডেল_ টাইপ একটি পূর্ণসংখ্যা টাইপ যা পয়েন্টার (যেমন সাইজ_ট বা স্বাক্ষরযুক্ত দীর্ঘ) ধারণ করতে সক্ষম।
আলেক্সি পোলোনস্কি

3

এইভাবে, কাজ করা উচিত:

std::stringstream ss;
ss << std::this_thread::get_id();
int id = std::stoi(ss.str());

লাইব্রেরি স্ট্রিম অন্তর্ভুক্ত মনে রাখবেন


ভাল লাগল, তবে কেন আপনি এটি একটি পূর্ণসংখ্যা বলে ধরে নিচ্ছেন? এটি হেক্স বা অন্য কিছু হতে পারে।
rustyx

আপনি যদি ব্যবহার করে থাকেন std::stringstreamতবে আপনি এটি ব্যবহার করে operator >>ইনটকে রূপান্তর করতে পারেন । আমি যদি নিশ্চিত যে এটি অবিচ্ছেদ্য তবে পরিবর্তে uint64_tপ্রকারের মতো পছন্দ করব । idintid
aniliitb10

3

থ্রেড :: get_id () ব্যবহার না করার মূল কারণ হ'ল এটি কোনও একক প্রোগ্রাম / প্রক্রিয়াতে অনন্য নয়। কারণ প্রথম থ্রেডটি শেষ হওয়ার পরে আইডিটি দ্বিতীয় থ্রেডের জন্য পুনরায় ব্যবহার করা যেতে পারে।

এটি একটি ভয়াবহ বৈশিষ্ট্য বলে মনে হচ্ছে তবে এটি সি ++ 11 এ কি আছে।


2

এটি আপনার জন্য থ্রেড_আইডি কী ব্যবহার করতে চান তার উপর নির্ভর করে; তুমি ব্যবহার করতে পার:

std::stringstream ss;
ss << std::this_thread::get_id();
uint64_t id = std::stoull(ss.str());

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

#include <sys/time.h>
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
uint64_t id = (ts.tv_sec % 1000000000) * 1000000000 + ts.tv_nsec;

এখন আপনার সিস্টেমজুড়ে অনন্য থ্রেড আইডির গ্যারান্টিযুক্ত।


অতিরিক্ত লোড operator<<যে কোনও কিছু মুদ্রণ করতে পারে , এটি সর্বদা পূর্ণসংখ্যার মুদ্রণ করবে তা অনুমান করা ভুল।
রাস্টিক্স

2

অন্য বিকল্প:

#include <atomic>

static std::atomic<unsigned long long> thread_counter;

unsigned long long thread_id() {
    thread_local unsigned long long tid = ++thread_counter;
    return tid;
}

X86 -৪-বিটে g ++ দ্বারা এই ফাংশনের জন্য উত্পন্ন কোডটি কেবলমাত্র:

_Z9thread_idv:
        cmp     BYTE PTR fs:_ZGVZ9thread_idvE3tid@tpoff, 0
        je      .L2
        mov     rax, QWORD PTR fs:_ZZ9thread_idvE3tid@tpoff
        ret
.L2:
        mov     eax, 1
        lock xadd       QWORD PTR _ZL14thread_counter[rip], rax
        mov     BYTE PTR fs:_ZGVZ9thread_idvE3tid@tpoff, 1
        mov     QWORD PTR fs:_ZZ9thread_idvE3tid@tpoff, rax
        ret
_ZGVZ9thread_idvE3tid:
        .zero   8
_ZZ9thread_idvE3tid:
        .zero   8

অর্থাত্ কোনও সিঙ্ক্রোনাইজেশন ছাড়াই একটি একক শাখা যা আপনি প্রথমবার ফাংশনটি কল করবেন তা বাদ দিয়ে সঠিকভাবে পূর্বাভাস দেওয়া হবে। এর পরে সিঙ্ক্রোনাইজেশন ছাড়াই কেবল একটি একক মেমরি অ্যাক্সেস।


@ ননসেন্সএটিএল: নিশ্চিত না যে আমি আপনার প্রশ্নটি বুঝতে পেরেছি ... এর জন্য thread_localইতিমধ্যে স্টোরেজ সময়কাল বর্ণনা করে tidstaticজন্য thread_counterকারণ আপনি এই সংকলন ইউনিট বাহিরে এটা প্রকাশ করার চাই না।
6502

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

4
@ সুইসফ্র্যাঙ্ক: এটি কেবল একটি সংখ্যা এবং প্রত্যাবর্তিত মূল্যটিতে আপনার খুব বেশি পড়া উচিত নয়: আপনি যখন জিজ্ঞাসা করেছিলেন তখন এটি নির্ধারিত হয়েছিল তা জানার কোনও আইনগত উপায় নেই :-)। 0একটি বৈধ আইডি এই সত্যটি সম্পর্কে যা একটি ভাল পয়েন্ট এবং পরিবর্তে প্রিঙ্ক্রেমেন্ট ব্যবহার করে স্থির করা যায়। আমি এটি করতে উত্তর পরিবর্তন করব।
6502

1

এই সমাধানটি কারও পক্ষে সহায়ক হতে পারে। এটিকে প্রথম বার বলুন main()। সতর্কতা: namesঅনির্দিষ্টকালের জন্য বৃদ্ধি পায়।

std::string currentThreadName(){
    static std::unordered_map<std::thread::id,std::string> names;
    static std::mutex mtx;

    std::unique_lock<std::mutex> lock(mtx);

    auto id = std::this_thread::get_id();

    if(names.empty()){
        names[id] = "Thread-main";
    } else if(names.find(id) == names.end()){
        std::stringstream stream;
        stream << "Thread-" << names.size();
        names[id] = stream.str();
    }

    return names[id];
}

স্ট্রিংস্ট্রিম ব্যবহার করবেন না, এটি ধীর গতির, std :: to_string
NoSenseEtAl
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.