استفاده از clang برای corss compile

اینم یه تجربه پراکنده دیگه!

خب بعد از مدتها دارم مینویسم و از این که این فرصت پیش اومده خوشحالم. موضوعی که الان میخوام در موردش بنویسم اینه که چطور میشه با استفاده clang که یک کامپالر جدید هست، کدی رو برای یک دستگاه embedded که اتفاقا کامپایلر gcc و کل زندگی خودش رو داره کامپایل کرد.

ممکنه بگید این به نظر غیر ممکن میاد اما مخصوصا توی C میشه یه چنین کاری انجام داد و clang و نسخه‌های جدید gcc قدرت این کار رو دارن که کد رو جوری کامپایل کنن که روی دستگاه‌های قدیمی کار کنه. چیزی که نیاز دارید اینکه اول به مجموعه کامپایلر دسترسی داشته باشید و اون کامپایلر حاوی چیزی به اسم sysroot باشه. کلا منظور از sysroot اینکه کل کتابخانه‌ها و ابزارهای لازم برای ساختن یک سیستم عامل کامل برای اون platform رو داشته باشید. دومین چیزی که نیاز دارید اینه که یه چیزی به نام target رو بدونید. این معمولا یه چیز سه بخشی هست که نشون میده cpu چیه، ساختار فایل اجرایی چیه، سیستم عاملی وجود داره یا نه. مثلا به چند مورد ازشون اینهاست armv7l-linux-gnueabihf یا arm-linux-gnueabihf که هر بخش از این سه گانه میگه چی به چیه.

یکی دیگه از چیزایی که مهم میشه اینه که کجا دنبال کامپایلرهای gcc بگرده که خود رو با اونها تطبیق بده خب اون رو هم باید بگید.

نکته بعدی که مهمه اینه که بگید از چه linker ای استفاده کنه. چرا؟ چون linker هست که زمان اجرا میاد و نرم افزار رو واقعا سر هم میکنه و قابل اجرا میکنه. مثلا اگه قرار باشه یه فایل تست رو کامپایل کنید احتمالا باید یه چنین چیزی رو اجرا کنید.

[/crayon]

حالا این تست رو که بتونید روی بردتون اجرا کنید میتونید اون رو با makefile یا CMake هم ترکیب کنید و چیزهای جالبی بدست بیارد

همین!

لذت برنامه‌نویسی: ساختن header فایل در cmake

اینم یه تجربه پراکنده دیگه!

با توجه به شلوغی‌های روز افزون زندگی، دارم سعی میکنم که به حد یک نوشته در ماه برسم. قبلا در مورد cmake نوشتم و حالا خواستم یه مورد دیگه که شاید به درد کسی بخوره رو بنویسم.

ما توی کارهایی که انجام می‌دیم سعی کردیم که ویژگی‌های مختلف نرم‌افزارمون قابل فعال و غیر فعالسازی باشه. هر مشتری هم نیازهای خاص خودش رو داره و یه زیرمجموعه از ویژگی‌ها رو تحویل میگیره. کاری که انجام داده بودیم استفاده از پیش‌پردازندهها برای این کار بود. بدین شکل که هر ویژگی یه پیش پردازنده مخصوص خودش داشت که فعال یا غیر فعال میشد.

این روند فعال و غیر فعال سازی همیشه با مشکلات جدی همراه بود بدین صورت که همکارها به علت زیاد بودن و متغیر بودن ویژگی‌ها برای مشتری‌ها بایستی این فایل رو زیاد تغییر میدادن و این تغییرات زیاد باعث می‌شد که خطای انسانی داشته باشن و یه چیزایی یادشون بره و یا اضافه کامپایل کنند و به مشتری بدن

کاری که ما کردیم این بود که این هدر خاص که همه این تعاریف توش بود رو بجای دستی ساختن به کمک cmake ساختیم به این شکل که

  1. یه متغیر از نوع cached تعریف کردیم که بشه به عنوان پارامتر زمان کامپایل اضافه بشه
  2. بر اساس اون متغیر برای هریک از ویژگی‌ها یک مقدار فعال و غیر فعال تخصیص دادیم
  3. از اون متغیرها استفاده کردیم و فایل اصلی رو ساختیم

نمونه این کار به این شکل هست که توی CMakeLists.txt یه چنین چیزی نوشتیم

توی فایل

هم یه چنین چیزی هست

و به این شکل این فایل بصورت اتوماتیک ساخته میشه و قابل استفاده است

برای اجرا کردن هم میشه با این دوتا دستور برای مشتری‌های مختلف کامپایل کرد

همین!

لذت برنامه نویسی: معرفی ninja سیستم build سریع

اینم یه تجربه پراکنده دیگه!

خب توی این پست یه ابزار جایگزین برای build کردن نرم‌افزار‌های c و c++ معرفی کنم به اسم ninja. داستان بوجود اومدنش از این قراره که آقای اوان مارتین که توی گوگل روی توسعه google chrome کار میکرده متوجه میشه روند کامپایل کدهاشون بیش از حد کنده و دست و آستینش رو بالا زدن که یه سیستم سریعتر برای build بنویسه که حاصل شد ninja. پیشنهاد میکنم در اولین گام سیستمتون رو اگه cmake نیست cmake کنید و در گام دوم از ninja استفاده کنید

ویژگی‌های ninja

خب ویژگی‌هایی که من بلدم از این قراره

  • سعی در انجام یک کار بصورت درست
  • سعی در خیلی سریع بودن
  • حداقل محدودیت روی نحوه کامپیال شدن کد و سپردن اونها به ابزارهای سطح بالاتر مانند cmake و gyp
  • تشخیص درست وابستگی‌ها
  • سرعت بر سادگی ارجحیت دارد

نمونه موردی

خب همونطور که قبلا هم گفته بودم نحوه کامپایل کدها منتقل شده بود به cmake و تنها کاری که لازم بود من انجام بدم دستورات زیر بود

برای کدهای ما در یک تست غیر علمی نتیجه‌های حدودی زیر برای ۲ یا ۳ بار اجرا بدست اومد

type make ninja
clean build ۶۰s ۱۳s
rebuild ۱۰s ۰٫۳s

همین!

غواصی در اعماق: متغیر محلی، عمومی و static در سی و نقش آنها در سرعت کامپایل

اینم یه تجربه پراکنده دیگه!

نوشتن این روزها سخت شده ولی من باز سعی میکنم که یه حضور کمرنگی در صحنه داشته باشم. این دفعه میخواستم در مورد یه موضوع جالب مرتبط با متغیر در سی بنویسیم

مشکل با متغیر محلی بزرگ

ما یه کد سی داشتیم که یه متغیر local با حجم حدودا ۱۰ کیلو بایت تعریف شده بود و مقدار دهی اولیه شده بود(عکس بود). همیشه کامپایلر به این قسمت از کد که میرسید کلی طول میکشید تا بتونه کد رو کامپایل کنه و همیشه اعصاب ما رو خورد میکرد. تا اینکه یه روز تصمیم گرفتیم مشکل رو حل کنیم. راه حل‌هایی که پیش رومون بود اینها:

  1. متغیر رو کلا حذف کنیم. که خب نمیخواستیم صورت مساله رو حذف کنیم
  2. متغیر رو global میکردیم که خب همه میتونستن تغییرش بدن و برامون بد بود
  3. متغیر رو static کنیم. این تفاوتی برای ما نداشت فقط میخواستیم امتحان کنیم.

خب اول راه دوم رو امتحان کردیم. اتفاقا هم زمان کامپایل کم شد و هم حجم باینری تولید شده. وقتی راه سوم رو هم امتحان کردیم دیدیم که نتیجه با راه دوم یکیه و حجم باینری کم شده و این خوب بود. چون راه دوم ویژگی‌های متغیر local رو داشت و عیب در دسترس بودن برای همه رو هم نداشت.

بررسی چرایی راه‌حل

سوالی که برای ما پیش اومد این بود که چرا یه تغییر اینجوری اینهمه هم توی سرعت هم حجم باینری موثره؟ برای جواب دادن به این ماجرا یکم رفتم تحقیق کردم:

  • همه باینری‌ها از بخش‌های مختلفی تشکلی شدن که اگه assembly یادتون باشه شامل اینهاست
  • متوجه شدم که متغیرهای global توی data segment تعریف میشن و متغیرهای local توی stack segment تعریف میشن.
  • متوجه شدم که متغیرهای static هم توی data segment تعریف میشن.

پس هرچی بود زیر سر data segment بود از اینجا به بعدش بازی شد حدس و گمان در مورد چرایی دوتا اتفاق:
– چرا سرعت کامپیال بیشتر؟ احتمالا به این دلیل که کامپایلر با data segment راحت‌تر کار میکنه تا stack segment. اما این حدس حجم باینری کمتر رو توجیه نمیکنه چون در این دوحالت حجم متغیر ثابت مونده
– چرا حجم باینری کمتر؟ برای اینکار یکم باید یادمون بیاد توی assembly که چطور یه متغیر local تعریف میشه منظورم هست. یه متغیر local عملا اینه که اول یه مقداری توی stack قرار میگیره و در یه مرحله دیگه اون مقدار از stack خارج میشه. که این هم نیاز به کد سمت code segment داره هم نیاز به حافظه سمت stack segment داره.

خب با این حالت ما به حدس دقیق تری از اتفاق پشت پرده رسیدیم. اونم اینه که وقتی متغیر توی data segment قرار میگیره یه بخش توسط کامپایلر تخصیص داده میشه و مقادیر هم توی اون خونه‌ها نوشته میشه. ولی وقتی قرار باشه اون متغیر توی stack segment قرار بگیره هم کد تولید میشه هم حافظه تخصیص داده میشه که منجر به طولانی تر شدن کل روند میشه.

امیدوارم به دردتون خورده باشه
همین!

لذت برنامه نویسی: makefile همه منظوره

اینم یه تجربه پراکنده دیگه!

من قبلا هم در مورد ابزارهایی مثل makefile و cmake که کامپیال کردن نرم‌افزارها بالاخص در زبان‌های C/C++ رو راحت می‌کنن نوشتم. البته باید بگم که هیچوقت بصورت کامل و درستی یاد نگرفتم که makefile چطوره و چطور میشه باهاش سرو کله زد تا اینکه در یکی از پروژه‌ها مجبور به استفاده از makefile شدم و با کمک یکی از دوستان یه پروژه متن‌باز پیدا کرد که یه مدل آماده makefile که توش تقریبا همه کارهای معمول انجام شده بود رو آورده بود.

توی این پست سعی درام که این پروژه متن‌باز رو معرفی کنم. خب اول باید بگم که شما برای هر پروژه جدید نیاز دارید که چندتا چیز رو مشخص کنید

  1. اینکه بدونید سورس پروژه کجاست.
  2. اینکه فولدرهایی که header ها توش قرار گرفته کجاست
  3. اینکه بدونید برنامه‌تون به چه کتابخانه‌هایی نیاز داره
  4. اینکه بدونید برنامه‌تون برای کامپیال شدن به کمک GCC به چه flagهایی نیاز داره.

بعد از اینکه این موارد رو دونستید و اونها رو پیدا کردید باید برید و بخشها مرتبط با این موارد رو توی makefile تغییر بدید به عنوان Customizable Section مشخص شده.

خب هر یک از این بخش‌های قابل تغییر برای خودش معنایی داره

  1. بخش مقدار flag هایی هست که برای کامپیال کردن فایلها با پسوند c به کار میره. لازه به ذکره که بدونید flagهای C با C++ می‌تونن تفاوت اساسی داشته باشند
  2. بخش نشون‌دهنده flag هایی هست که در فاز link قرار به linker داده بشه و با استفاده از اون کتابخانه ها شناسونده بشن. یه مثال برای این مقدار هست که میگه کتابخانه مورد نیاز این نرم‌افزار هست
  3. بخش بخشی هست که نشون میده flagهای کامپایل c++ چیا هستن. مثلا که نشون میده میخوایم از استاندارد C++ 2011 استفاده کنیم. فولدر headerها رو هم توی این بخش اضافه می‌کنیم
  4. اگه به هر دلیل بخوایم flagهای دیگه ای به linker بدیم از استفاده می‌کنیم.
  5. سورس نرم‌افزار رو با جاهایی که سورس قرار گرفته رو نشون میده.
  6. و در نهایت نام نرم‌افزار رو نشون میده.

امیدوارم با این توضیحات کوتاه دستتون اومده باشه که چه کاری رو به چه صورت باید انجام بدید و در صورت هرگونه سوالی کامنت بگذارید و یادتون نره که من هنوز makefile رو بخوبی بلد نیستم! 😉

غواصی در اعماق: Future ها در C++

اینم یه تجربه پراکنده دیگه!

خب من به مسائل مرتبط با کارایی و اجرای غیر همزمان کدها یا همون async رو دوست دارم. یکم هم تجربه کد نویسی رو باهاشون دارم. توی این پست میخوام بصورت ساده یکی از روشهای این پیاده‌سازی رو براتون توضیح بدم. خیلی دنبال این نیستم که بگم دقیقا این روش کجاها به درد میخوره و چرا به درد میخوره بلکه هدف یه معرفی اولیه است. انشالا در یه پست مفصل بیشتر توضیح میدم. کل موضوع حول دوتا واژه promise و future میگرده که در c++0x11 معرفی شده.

داستان از این قراره که در مدل sync شما یه تابع رو صدا میزنی و منتظر میمونی که جواب به شما برگرده. اما در این مدل بعد از صدا زدن یک تابع بجای منتظر موندن برای جواب همون موقع یک مقدار از نوع future برمیگرده که تا زمانی که تابع صدا زده شده به اتمام نرسیده مقداری نداره. بعد از تموم شدن تابع مقدار future همون مقداری هست که نتیجه محاسباته. حالا اگه از منظر تابعی که صدا زده میشه نگاه کنیم اون تابع خروجیش از نوع promise هست که بعد از اتمام اون رو پر میکنه و برمیگردونه. همین!

حالا این نمونه کد رو هم ببنید که یه مثال ساده از future و promise هست.

مثالها از اینجا گرفته شده. اولین مثال از این قرار که ما یه تابع که خروجیش void هست رو با استفاده future مورد استفاده قرار می‌دیم.

اتفاقی که اینجا میفته اینه که با استفاده از ‍

ما سعی میکنیم که اون تابع رو در یک thread دیگه اجرا کنیم و نتیجه رو بصورت یک future از اون بگیریم. با اجرای

مطمئن می‌شیم که اجرای اون thread به اتمام رسیده.

مثال دوم یه محاسبه بصورت غیر همزمان اجرا میشه و نتیجش به کمک future برگردونده میشه

امیدوارم که این بدردتون خورده باشه.
همین!

ویرکارایی: سرور سوکت با کارایی بالا در C++

اینم یه تجربه پراکنده دیگه!

یه مدته که اینجا حرف برنامه نویسی زده نشده و میترسم «عنودان بد گهر» فکر کنن که من برنامه نویسی رو فراموش کردم. پس این دفعه سعی میکنم یه موضوع عمیق که به کارایی سرور سوکت مربوط میشه رو بنویسم.

توی یکی از پروژه‌هایی که من انجام میدادم مهمترین نیازمندی این بود که با کارایی بالا از سوکت UDP اطلاعات رو دریافت و پردازش کنم. کل پردازش یه سری عملیات به نسبت ساده ریاضی بود و کل ماجرا توی کارایی اون سوکت UDP خلاصه میشد. خب اولین راهی که پیش من بود این بود که اولا تحقیق و دوما آزمایش کنم. توی تحقیقاتم با توجه به مطلبی که توی این کتاب در مورد Nginx خونده بودم و نتیجه جستجو‌هام به این نتیجه رسیدم که بایستی بصورت Async سوکت‌ها رو مدیریت کنم. توی تحقیقاتم با توجه به معلوم نبودم پلتفرم توسعه با استفاده از Node.JS و C# نمونه‌هایی پیاده کردم و تست کردم و الحق که به نتایج قابل قبولی تو مایه‌های ۱۰۰۰ یا ۱۰۰۰۰ پکت در ثانیه رسیدم. توی C و C++ راه حل‌های متفاوتی برای این کار ارائه شده بود که بهترینش به نظرم کاری بود که توی boost::asio انجام شده بود که کلا روند async رو برای سوکت‌ها فایلها و تایمرها پیاده‌سازی کرده بود. نتیجه استفاده از این کتابخانه بسیار خوب بود و تا الان فراتر از حد نیاز کارایی رو فراهم کرده. کد هم به شدت ساده است. کل سرور در حد ۲۰ خط پیاده‌سازی شده.

اما برم سراغ یه مقدار جزئیات پیاده‌سازی که این کتابخونه از چشم من دور کرده بود. برای اینکه بتونیم کارایی خوبی داشته باشیم بایستی بتونیم با یه روندی منتظر سوکت‌ها نمونده و در صورتی که براشون اتفاق خاصی افتاد اونها رو بررسی کنیم. به این حالت بایستی یه حلقه داشته باشیم که بصورت بی‌پایان اجرا میشه و منتظر رخدادهاست که معروف به حلقه رخداد هست. همانطور که میدونید سوکت‌ها اصلتا در  کرنل مدیریت میشن و اطلاع از رخدادها رو کرنل برای ما فراهم میکنه. پس اگه بخوایم بصورت غیرهمزمان(async) اونها رو بررسی کنیم چاره‌ای نداریم که  کرنل این کار رو برای ما انجام بده. به این شکل که مکانیزمی داشته باشه که ما بتونیم بگیم که حواست به این سوکت‌ها باشه و هر وقت خبری شد خبرم کن. توی سیستم عامل‌های مختلف کرنل مکانیزم‌های متفاوتی برای این کار رو فراهم میکنه:

  • kqueue: مکانیزمی هست که توی خانواده BSD فراهم شده
  • epoll: مکانیزمی هست که توی کرنل لینوکس فراهم شده
  • event port: در سولاریس مورد استفاده قرار گرفته
  • Overlapped I/O: به پیاده سازی اختصاصی این ماجرا در ویندوز گفته میشه.
  • Input/Output Completion Port: روندی است که ویندوز و سولاریس اون رو پیاده‌سازی کردن.

البته باید بگم که برخی از این مکانیزم‌ها به سیستم عامل‌های دیگه هم پورت شدن و توی اونها هم قابل دسترسی هستند.

خب اگه ما بخوایم کدی بنویسیم که از یکی از این مکانیزم‌ها به درستی استفاده کنه و قابل پورت به سیستم عامل‌های دیگه باشه میرسیم به همون boos::asio که دقیقا همین کار رو کرده وسیستم عامل‌های مختلف رو هم پیشتیبانی میکنه. پیاده‌سازی این مکانیزم‌ها معمولا یه سختی هم داره و اون اینه که شما بایستی با system call های کرنل اون سیستم عامل آشنایی خوبی داشته باشی که در اکثر برنامه‌نویسا نیست که این باعث سخت شدن نگهداری کد میشه.

در مجموع استفاده کردن این کتابخانه‌ها که هم بخوبی تست شدن، هم ثبات کافی دارن باعث سادگی کار کاهش زمان مورد نیاز برای توسعه میشه. اما یادتون باشه که این سادگی هزینه داره و اگه یه روزی برسه این برنامه جواب کارتون رو نده شما باید آستیناتون رو بالا بزنید و کد رو یه شخم حسابی بزنید!

همین!

لذت برنامه نویسی: کتابخانه کار با سوکت در c++

اینم یه تجربه پراکنده دیگه!

یکی از کارهای متفاوتی که من میکنم اینه که توی لینوکس به زبان c++ برنامه نویسی میکنم. معمولا توی برنامه‌هایی هم که مینویسم نیاز داره که با سوکت‌های کار کنم. معمولا هم کارها طوریه که معمولا دسترسی به سوکت مهمه. من گشتم و یه سری کتابخانه برای اینکار پیدا کردم. به نظرم جالب بود که اینلیست این کتابخانه ها رو اینجا بگم.

قبل از معرفی بگم چرا به این دست کتابخانه‌ها نیازه. اولا اگه تجربه کار با سوکت‌ها رو داشته باشید میدونید در مجموع کار کردن با سوکت‌ها سخته. اولا اینکه برای ایجاد یک اتصال نیاز به انجام مقدماتی هست (مثلا تبدیل آی پی به یه قالب خاص یا تبدیل نام سایت به آی پی) همچنین یه سوکت در c++ حالت‌های گوناگونی داره که کار کردن و هندل کردن تمام این حالت‌ها معمولا کار سختیه. نکته بعدی اینه که روند کار با سوکت توی ویندوز، لینوکس و سایر سیستم‌ عامل ها متفاوت و نوشتن یه کد potable کار سخت تریه. پس معقوله شما با دادن یه هزینه معقول در زمینه کارایی و سایز کد نرم‌افزار، سطح بالاتر با سوکت سر و کله بزنی!

لیست این کتابخانه‌های اینا هستن:

  • Boost.Asio: این کتابخانه یه کتاب‌خانه سریع و خوب برای کار با سوکت‌هاست و شما به سوکت بصورت مستقیم دسترسی داری
  • ACE: از دل دانشگاه در اومده و به نظر خوب میاد. سوکت‌ها بصورت مستقیم در دسترس هستند اما عملیات‌های سطح بالاتر مثل message routing رو پشتیبانی میکنه
  • C++ Network Library: تا اونجایی که نمونه‌های و مستنداتش رو بررسی کردم این کتابخانه برای کارهای سطح بالا تر مثل http نوشته شده
  • POCO: این کتابخانه مجموعه ای از ابزارهای سطح بالا برای برنامه نویسی در اختیار شما قرار میده که شبکه و سوکت‌های یکی از اونهاست
  • Qt: این کتابخانه هم مجموعه بسیار بزرگی از ابزارها داره که یکی از اونها شبکه و سوکت هست
  • Raknet: یه کتابخانه کار با شبکه است که هم توی pc و هم توی کنسولهای بازی کاربرد داره
  • ZeroMQ: این کتابخانه بیشتر از اینکه یه کتابخانه شبکه باشه یه کتابخانه message queue هست که به شما کمک میکنه ارتباط بین چندتا اپلیکیشن رو بصورت قابل اعتمادی ایجاد کنید
  • nanomsg: این کتابخانه هم بیشتر به عنوان message queue طراحی شده
  • libevent: اصالتا یه سیستم برای پشتیبانی از سیستم‌های async هست که در جاهای بسیاری استفاده شده و بخشی از اون هم کارهای شبکه و سوکت رو انجام میده
  • Apache APR: یکی دیگر از کتابخانه‌های عموی با پشتیبانی از سوکت است
  • yield: یک کتابخانه نوشتن نرم‌افزارهای تحت وب است
  • wvstreams: یک کتابخانه قدیمی برای کار با شبکه است که سال‌هاست بروزرسانی نشده
  • libcurl: یک کتابخانه بسیار قوی برای کار با پروتکل‌های شبکه نه سوکت‌هاست که بیشتر پروتکل‌های معمول رو پشتیبانی میکنه
  • libuv: یک کتابخانه برای کار با async IO بوده که از شبکه و سوکت هم پشتیبانی میکنه. جالبه بدونید که توی nodejs هم از این کتابخانه استفاده شده.
  • SFML’s Network Module: این کتابخانه هم مثل qt یک کتابخانه چند منظوره است که از سوکت‌ها و شبکه هم پشتیبانی میکنه.

من خودم به شخصه تنها از Boost.Asio استفاده کردم و انشاا… اگه عمری بود بیشتر در مورد این کتابخانه و نمونه کدها و ویژگی‌های دیگشون مینویسم.

همین!

لذت برنامه نویسی: اتصال به پایگاه داده در c++

اینم یه تجربه پراکنده کوتاه دیگه!

من اخیرا توی یکی از پروژه‌هام که به زبان c++ نوشته شده نیاز داشتم که از یک شبه ORM به نسبت سبک استفاده کنم چند تا ویژگی داشته باشه. این ویژگی‌ها عبارتند از:

  1. متن باز باشه
  2. وابستگی‌های زیادی نداشته باشه
  3. حداقل از sqlite و mysql و postgres پشتیبانی کنه
  4. استفاده ازش راحت باشه

گزینه‌های زیادی جلوی روم نبود

یادمه نمیاد چرا odb رو از لیست انتخاب‌هام حذف کردم. اما qxrm به دلیل اینکه نمیخواستم یه وابستگی جدید(وابستگی به qt) به کدم اضافه کنم استفاده نکردم. و در انتها از soci استفاده کردم. کار با soci به نسبت راحته و فقط کافیه که نمونه کد ازش ببینید. تقریبا هم توی تمام توزیع‌های لینوکس قابل استفاده است

همین.

 

لذت برنامه نویسی: تعداد هسته‌های cpu در c++11

اینم یه تجربه پراکنده دیگه!

ایندفعه گفتم یه مطلب کوتاه بنویسم. اگه بخواید توی c++11 که آخرین نسخه زبان c++ هست بصورت اتوماتیک تعداد هسته‌های cpu یا همون تعداد thread ها رو دربیارید فقط کافیه که از کتابخانه‌های استاندارد استفاده کنید. نمونه کد هم به شکل زیر هست:

واسه اطلاعات بیشتر هم به این لینک مراجعه کنید

همین!