Stuff for Geeks
156 subscribers
181 photos
38 videos
178 files
576 links
Admin: @the_mhbr
Download Telegram
Stuff for Geeks
همزمانی در سی‌پلاس‌پلاس پست یک خب سی‌پلاس‌پلاس و stl یه مجموعه کاملی برای concurrency دارن. مهم ترینش شاید بشه گفت کلاس std::thread باشه که ریسورسای لازم یک ثرد رو از سیستم عامل می‌گیره. فرض کنین بخوایم یه تابع رو به شکل همزمان اجرا کنیم شکل کلی برنامه اینجوری…
همزمانی در سی‌پلاس‌پلاس
پست ۲

تو این پست میخوام در مورد مشکلی که آخر پست یک بهش اشاره شد، بگم. مشکلی که ما با پاس دادن متغیر به std::thread که یه کانستراکتور هست، داریم. فرض کنید که یه تابع مثل زیر داریم و میخوایم اون رو به std::thread پاس بدیم:
void someFunction(std::string someString){
//some processing
}

خب حالا فرض کنید یه ثرد با این تابع بسازیم:

int main(){
std::thread th(someFunction, "some string");
th.join();
return 0;
}

این کد مشکل ساز خواهد بود

هرچند که اینجا کار میکنه، ولی در ادامه می‌بینیم که این کد مشکلات بزرگی داره

دلیلش اینه که متنی که داخل دابل کوت قرار داره، یه آبجکت استرینگ نیست و درحقیقت یه cons char* عه.

درواقع someFunction یه استرینگ میگیره ولی متد std::thread یه سری ورودی میگیره که جنسشون ربطی به اینکه جنس پارامترهای someFunction چیه نداره. پس اینجا به مشکل میخوریم. مشکل وقتی جدی میشه که پارامتری که داریم به std::thread پاس میدیم، یه پوینتر اتوماتیک باشه که از اسکوپ خارج شده باشه ولی ثرد بهش نیاز داشته باشه. اینجاست که کدمون خطری میشه.

راه حل چیه؟
راه حل اینه که واضحا به کامپایلر بگیم که میخوایم استرینگ پاس بدیم. کد زیر، کد درسته:
int main(){
   std::thread th(someFunction, std::string("some string"));
   th.join();
   return 0;
}

همین قضیه برای رفرنس برقراره. البته ایندفعه کامپایلر ارور میده. مثلا کد زیر، خطای کامپایل خواهد داشت:
void foo(int& someRef){
//...
}

int main(){
int i=10;
std::thread th(foo, i);
th.join();

return 0;
}

اینجا هم باید به کامپایلر واضح بگیم که باید رفرنس پاس بده. برای اینکار باید از std::ref استفاده کنیم که یه wrapper برای reference هست:

void foo(int& someRef){
//...
}

int main(){
    int i=10;
    std::thread th(foo, std::ref(i));
    th.join();

    return 0;
}

و کد کامپایل خواهد شد.

ادامه

#programming
#cpp
#concurrency
Stuff for Geeks
همزمانی در سی‌پلاس‌پلاس پست ۲ تو این پست میخوام در مورد مشکلی که آخر پست یک بهش اشاره شد، بگم. مشکلی که ما با پاس دادن متغیر به std::thread که یه کانستراکتور هست، داریم. فرض کنید که یه تابع مثل زیر داریم و میخوایم اون رو به std::thread پاس بدیم: void som…
همزمانی در سی‌پلاس‌پلاس
پست ۳

تو این پست میخوام درمورد std::jthread که از C++17 به stl اضافه شده یه صبحتی بکنیم.
این کلاس یه خاصیت خوب داره:
توی دیستراکتورش، چک میکنه اگه ثرد جوین نشده بود جوینش میکنه.

چرا این خاصیت خوبه؟
چون ما معمولا یه ثرد که می‌سازیم، چندین خط بعد جوین یا دیتچش میکنم و به همین دلیل ممکنه بین ساختنش تا جوین شدنش اکسپشن رخ بده یا اتفاقای دیگه که باعث شده ثرد جوین نشه. و همونطور که قبلا گفتیم، اگه یه ثرد عادی (std::thread) جوین نشه و دیستراکتورش صدا زده بشه، برنامه کامل بسته میشه(std::terminate).
پس خوبه که بجای std::thread که توی C++11 اضافه شد، از std::jthread استفاده کنیم.

ادامه

#cpp
#programming
#comcurrency
Stuff for Geeks
همزمانی در سی‌پلاس‌پلاس پست ۳ تو این پست میخوام درمورد std::jthread که از C++17 به stl اضافه شده یه صبحتی بکنیم. این کلاس یه خاصیت خوب داره: توی دیستراکتورش، چک میکنه اگه ثرد جوین نشده بود جوینش میکنه. چرا این خاصیت خوبه؟ چون ما معمولا یه ثرد که می‌سازیم،…
همزمانی در سی‌پلاس‌پلاس
پست ۴

خب
حالا که چندین ثرد داریم، اگه خواسته باشیم یه حافظه‌ای بین اینا به اشتراک بذاریم(shared_momry) باید چیکار کنیم؟

تعداد زیادی از مشکلات و مباحث همزمانی از اینجا شروع میشه.

مشکل اول، race condition:
یه مثال معروفی هست برای اینکه بفهمیم این مشکل چیه. فرض کنید رفتین سینما یا تئاتر و ده‌تا از مسئولین سالن دارن برای هر صندلی بلیت میفروشن. (صندلی‌ها میشن shared memory و ماموران فروش میشن ثردها)
حالا اینجا مشکل اولی که پیش میاد اینه که اگه ماموران فروش خوب هماهنگ نشده باشن، ممکنه یه صندلی رو به دو یا چندتانفر بفروشن که قاعدتا کار اشتباهیه

اینجا هم همین مشکل هست
فرض کنید دوتا ثرد بخوان به عددی که تو یه خونه از حافظه هست رو بخونن(read) و مقدارش رو یک واحد اضافه کنن(write).

این مورد هم توجه داشته باشید که دیتا متاسفانه کش میشه. ینی از مموری اصلی(رم) خونده میشه و تا یه مدت کوتاهی توی سی پی یو هست.
حالا فرض کنید مقداری که توی حافظه هست عدد ده باشه.
ثرد یک این مقدار رو میخونه(۱۰) و این مقدار خونده شده میاد تو کش.
میاد یکی بهش اضافه کنه ولی تا بخواد اضافه کنه، ثرد دو چون همزمان داره اجرا میشه، ممکنه بیاد و بخواد عملیات خوندن رو انجام بده و چون توی کش مقدار ده رو داریم، عدد ده رو خواهد خوند. بعد هر ثرد یکی اضافه میکنن به این مقدار که میشه یازده و توی مموری مینویسنش.

انتظارمون این بود به عدد دوازده توی مموری برسیم(چون دوتا ثرد هرکدوم داشتن به یه متغیر یه واحد اضافه میکردن) ولی ممکنه به عدد یازده هم برسیم.

برای رفع این مشکل ساختارهای متفاوتی در سطح نرم‌افزاری و سخت افزاری داریم. در سطح نرم‌افزاری یا دقیق تر سیستم عاملی، mutex و semaphore و در سطح سخت‌افزاری atomic operation ها رو داریم که در ادامه بررسی خواهند شد

ادامه

#programming
#cpp
#comcurrency
Stuff for Geeks
همزمانی در سی‌پلاس‌پلاس پست ۴ خب حالا که چندین ثرد داریم، اگه خواسته باشیم یه حافظه‌ای بین اینا به اشتراک بذاریم(shared_momry) باید چیکار کنیم؟ تعداد زیادی از مشکلات و مباحث همزمانی از اینجا شروع میشه. مشکل اول، race condition: یه مثال معروفی هست برای اینکه…
همزمانی در سی‌پلاس‌پلاس
پست ۵

توی این پست میخوام درمورد mutex صحبت کنم. توی پست قبلی گفتیم که دسترسی چند ثرد به یه ریسورس، مشکل‌ساز میشه و باید کنترل شه. یکی از ابزارهامون همین mutex هست. با mutex میتونیم قسمت‌هایی از کد که با یک shared memory کار دارن رو بهشون یه برچسب بزنیم و بگیم که این تیکه باید توسط یک ثرد کامل انجام بشه و تا انجام نشده ثرد دیگه‌ای نباید مداخله کنه. اینجوری یه بخشی از مشکلات حل میشه. درواقع mutex مخفف mutual exclusive هست که همین قضیه رو نشون میده.

به اون تیکه از کد که برچسب زدیم بهش هم، critical section میگن.

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

خب فرض کنید میخوایم یه تیکه از کد رو بگیم که mutual exclusive هست. اینجوری میشه:
#include <mutex>

using std::mutex;

int main(){

mutex m1;
...
//Start of critical section
m1.lock()
...
m1.unlock()
//End of critical section
}

البته چون لاک و آنلاک کردن دستی میوتکس یه آنتی‌پترن حساب میشه، یه سری کلاس‌های دیگه مثل lock_gaurd داریم که یه میوتکس میگیره، لاک میکنه و با خارج شدن از اسکوپ، اون رو آنلاک میکنه.
یعنی کد بالا با lock_gaurd به شکل زیر تغییر میکنه:
//Start of critical section
{
lock_gaurd lock1{m1} //m1 is locked now

}
//End of critical section
//m1 gets unlocked in lock1 destructor (RAII)

و چندین کلاس دیگه هم داریم. مثلا scoped_lock که توی C++17 اضافه شده، نسخه بهتر lock_gaurd حساب میشه. این کلاس یک یا چندتا میوتکس میگیره و جوری لاک و آنلاکشون میکنه که dead lock پیش نیاد.

حالا dead lock چیه؟
اگه دوتا ریسورس r1 و r2 و میوتکسهای m1 و m2 رو داشته باشیم و دوتا ثرد t1 و t2 که به این ریسورسها نیاز دارن، اگه ترتیب لاک و آنلاک شدن بد باشه به ددلاک میخوریم.
مثلا t1 به r2 نیاز داره که توسط m2 و t2 لاک شده و شرط آنلاک شدنش دسترسی به r1 عه درحالیکه r1 توسط m1 و t1 لاک شده.

اینجاست که هیچ وقت ریسورسها آنلاک نمیشن و ثردها تا همیشه تو صف انتظار میمونن!

پس scoped_lock این مشکل رو حل میکنه ولی بازم تاکید میکنم که استفاده از میوتکس خوب و درسته درصورتیکه طراحی درست باشه

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

ادامه


#cpp
#concurrency
#programming
push_back(c++11)?
emplace_back(c++17)?
مسئله اینست

فرض کنید که یک کلاس با تعداد زیادی فیلد(سایز زیاد) داریم و میخوایم یه وکتور از این تایپ داشته باشیم:
class BigStructure{
//lots of fields
//...
};

خب حالا چجوری داخل یه وکتور اشیائی ازین کلاس رو وارد کنیم؟
آیا پوش بک بهینست؟

برای جواب به این سوال، فرض کنید یه شیئ ساخته شده از کلاس حجیممون داریم. مثلا اسمش b باشه. وکتورمون هم فرضا v باشه، وقتی می‌نویسیم:
v.push_back(b)

اتفاقی که معمولا میوفته اینه که b کپی میشه و داخل v پوش میشه.
این کپی کردنه مشکل ماجراست که باعث میشه ریسورس زیادی از دست بدیم پس چه کنیم؟
از تابع emplace_back استفاده میکنیم. این تابع، مزیتی که داره اینه که با گرفتن پارامترهای لازم برای ساختن یه شیئ از کلاس مدنظر، اون شیئ رو میسازه و move میکنه داخل وکتور.(اگه نمیدونین move چیه سرچ کنید move semantics)
خلاصه که اینجوری دیگه شیئ اضافی کپی نمیشه و خیالمون راحته.

البته میشه اینجوری هم کار رو درآورد:
v.push_back(std::move(b))

ولی بعد اینکار b از بین میره و دسترسی بهش باعث undefined behavior میشه.

خب پس اگه لازم بود شیئ بسازین و داخل وکتور قرار بدین، همیشه emplace_back بهتر از push_back خواهد بود.

#cpp
#programming
Stuff for Geeks
push_back(c++11)? emplace_back(c++17)? مسئله اینست فرض کنید که یک کلاس با تعداد زیادی فیلد(سایز زیاد) داریم و میخوایم یه وکتور از این تایپ داشته باشیم: class BigStructure{ //lots of fields //... }; خب حالا چجوری داخل یه وکتور اشیائی ازین کلاس رو وارد…
در همین راستا، خوبه
try_emplace (C++17)
و
std::map::emplace (C++11)

رو که توابعی هستن از std::map
و std::unordered_map بهش یه اشاره بکنیم.

وقتی یه مپ داشته باشیم و بخوایم یه key و value داخلش قرار بدیم، چندتا راه وجود داره
بدترین ولی قدیمی ترین راه:
map[key] = value
خب مشکل ما با این کار اینه که اگه یه key و value از قبل وجود داشته باشن که کلیدش با کلیدی که داریم میدیم یکی باشه، اون value قبلی از بین میره و value جدید جاش میشینه.

راه دوم استفاده از emplace هست که یه std::pair میگیره، key و value رو میسازه و اگه key ساخته شده از قبل وجود نداشت، جفت key و value رو اضافه میکنه به مپ. مشکل ما اینجا اینه که اول key و value ساخته میشن و بعد key تست میشه که توی مپ هست یا نه که این ساخته شدن قبل از تست جالب نیست.

راه سوم استفاده از try_emplace هست. توی این روش، دقیقا مثل emplace ورودی یکسانه ولی فقط key ساخته میشه و چک میشه که داخل مپ نباشه و اگه نبود، value از روی پارامترهای موجود توی مپ ساخته میشه و داخل مپ قرار داده میشه.

راه های دیگه ای هم هست که خیلی واردش نمیشم(مثلا insert) ولی خب چندان تاثیری هم روی بحث الانمون ندارن

پس اگه خواستین توی مپ چیزی قرار بدین، try_emplace رو درنظر بگیرین.
#cpp
#programming
Return value optimization
یا
RVO
در سی‌پلاس‌پلاس

این قابلیت که واجبه هر برنامه نویس C++ ای خوب باهش آشنا باشه از کپی شدن های اضافه جلوگیری میکنه.
مثلا فرض کنید یه تابع داریم که از یه کلاس دلخواهی یه آبجکت میسازه و برمی‌گردونه:
SomeType myFunction(){
SomeType obj{//parameters}

return obj;
}

int main(){
SomeType myObj = myFunction();
}

خب اگه یکم دقیق به این کد نگاه کنیم، می‌بینیم که توی تابع یه شیئ ساخته میشه(صدا زده شدن کانستراکتور) و بعد کپی کانستراکتور صدا زده میشه و آبجکت داخل مین ساخته میشه. پس اینجا آبجکت داخل تابع ساخته شد، کپی شد و از بین رفت که خوب نیست چون کپی شدن الکی و اضافی داریم.

برای حل کردن این مشکل از c++11 به بعد می‌تونیم از std::move استفاده کنیم ولی فعلا کاری به این قضیه نداریم.

از std::move که بگذریم، RVO اضافه میشه و کپی شدن‌های اضافی رو پیدا و حل می‌کنه. یعنی اگه همین کد بالا رو تست کنید و بدون هیچ فلگ خاصی با g++ یا clang++ کامپایل کنین می‌بینید که فقط کانستراکتور عادی یکبار صدا زده میشه و خبری از کپی شدن ها نیست ولی اگه به کامپایلر فلگ های
-std=c++11
و
-fno-elide-conatructors
رو پاس بدین، می‌بینین که یبار کانستراکتور عادی و یه بار کپی کانستراکتور صدا زده میشه.

بگذریم
دو مدل rvo داریم. مدل اول برای متغیرهایی هست که واقعا ساخته میشن و اسم دارن که بهش NRVO یا
Named Return Value Optimization
هم میگن و مدل دوم که برای آبجکت‌های موقت یا بدون اسم یا تمپورری هست و بهش URVO یا RVO میگن.

یه نکته که خوبه بدونیم اینه که از c++98 کامپایلرها توصیه شدن که از URVO و NRVO استفاده کنن و پیاده‌سازیش کنن ولی فقط URVO از c++17 به بعد اجباری شده و همه کامپایلرها باید پیاده‌سازیش کنن. برای همین بود که توی کد اول پست، یه آبجکت named ساختیم و به کامپایلر فلگ‌های مربوطه رو پاس دادیم تا بتونیم NRVO رو غیرفعالش کنیم.

یه سری نکات ریز هم داره این قضیه. مثلا اینکه اگه return type و تایپ آبجکت با هم یکی نباشن (مثلا ساب‌کلاسش باشه)، rvo کلا کار نمیکنه. یا اگه توی تابع چند تا برنچ جداگونه چیزی return کنن، باز این قابلیت غیرفعال میشه

#cpp
#programming
شدیدا کتاب قوی و خوبیه

#programming
#cpp
CRTP in C++

فرض کنید که یه کلاس بیس داریم و چندین کلاس که ازین کلاس ارث بری کردن.
فرض کنید توی کلاس بیس یه تابع virtual و چندتا تابع عادی داشته باشیم و داخل یکی ازین توابع عادی بخواهیم این تابع virtual رو صدا بزنیم. مشکلی که اینجا خواهیم داشت، اینه که صدا زدن تابع virtual با this اتفاق خواهد افتاد و نتیجتا تابع virtual کلاس بیس صدا زده خواهد شد و نه تابع کلاس های ارث بری کرده.

class Base{

public:
virtual void virt_func(){
//Base implementation
}
void normal_func(){
virt_func();
}
};

خب حالا فرض کنید لازم داریم که توی کلاس والد، توابع virtual کلاس‌های فرزند رو صدا بزنیم. چه کنیم؟

یا فرض کنید درکلاس های فرزند یک تابع استاتیک وجود دارد که پیاده سازی آن توسط کلاس فرزند انجام گرفته است. برای صدا زدن این توابع درون تابع والد باید چه کرد؟

اینجاست که CRTP وارد بحث میشه. توی این پترن، میایم تایپ کلاس های فرزند رو درقالب تمپلت به کلاس بیس پاس میدیم:
template <typename T>
class Base{
};

class Derived: public Base<Derived>{
};

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

#cpp
#programming
چند الگوریتم مفید در c++

1) std::min_element
با این الگوریتم میتونین کوچیک ترین عضو یه کانتینر رو پیدا کنین
و البته برای مقایسه المنتای کانتینر میتونین یه لامبدا بهش پاس بدین:
std::vector<double> vec;
double min=*std::min_element(vec.cbegin(), vec.cend());

std::vector<MyType> vec2;
MyType min = *std::min_element(vec2.cbegin(), vec2.cend(), [](const MyType& lhs, const MyType& rhs){ return lhs.field1<rhs.field1;});

توجه کنید که این تابع ایترتور برمی‌گردونه و برای همینه که از * استفاده شده.

2) std::max_element
مثل الگوریتم قبلی با این تفاوت که ماکسیمم برمی‌گردونه

3) std::minmax_element
این الگوریتم هردوی مینیمم و ماکسیمم رو با یه std::pair از ایترتورها برمی‌گردونه.

4) std::rotate
این الگوریتم کانتینر اعضای کانتینر رو به یه نحو خاصی جابجا میکنه

ورودی تابع سه تا فوروارد ایترتوره. مثلا
std::rotate(vec.begin(), vec.begin()+3 , vec.end());

با صدا زدن اینجوری این الگوریتم روی کانتینر vec، هرچی المنت قبل vec.begin()+3 باشه میره آخر کانتینر و نتیجتا vec.begin()+3 میشه سر کانتینر جدیدمون.
پس اعضای قبل پارامتر دوم این الگوریتم به آخر الگوریتم منتقل میشن تا این پارامتر بشه اول کانتینرمون

4) std::partition
این الگوریتم دوتا ایترتور برای اول و آخر کانتینرمون میگیره و یه لامبدا یا فانکشن پوینتر که boolean برمی‌گردونه و اعضای کانتینر رو جوری می‌چینه که اون اعضایی تابع براشون true هست میان اول کانتینر و اونایی که false ان میرن آخر کانتینر. البته ترتیب اعضا ممکنه بهم بخوره. مثلا فرض کنین یه وکتور از بولینها مثل زیر داشته باشیم:
0,1,0,1,1
و تابع ورودی به فانکشنمون هم صرفا ترو یا فالس این بولین رو برگردونه. نتیجش این میشه که بعد از اجرای الگوریتم، وکتورمون به شکل زیر تبدیل میشه:
1,1,1,0,0

حالا مشکلی که داره اینه که ممکنه ترتیب المنتها حفظ نشه و خب اگه خواسته باشیم که حتما ترتیب حفظ بشه الگوریتم std::stable_partition رو باید استفاده کنیم.

#Programming
#cpp
Stuff for Geeks
همزمانی در سی‌پلاس‌پلاس پست ۵ توی این پست میخوام درمورد mutex صحبت کنم. توی پست قبلی گفتیم که دسترسی چند ثرد به یه ریسورس، مشکل‌ساز میشه و باید کنترل شه. یکی از ابزارهامون همین mutex هست. با mutex میتونیم قسمت‌هایی از کد که با یک shared memory کار دارن رو…
همزمانی در سی‌پلاس‌پلاس
پست ۶

توی این پست میخوام درمورد deadlock بیش‌تر صحبت کنم.
اصولا deadlock موقعی پیش میاد که به هر دلیل دوتا ترد به شکل دایره وار منتظر همدیگه بمونن. یکی از دلایل این انتظار میتونه میوتکس(میوتک؟)ها و یا درواقع ریسورس‌ها باشن ولی دلایل دیگه مثل طراحی بد هم میتونه باعث انتظار و deadlock بشه.

راه حل چیه؟
اولین و شاید بشه گفت مهم ترین نکته برای رفع deadlock اینه که قاعده زیر رو رعایت کنیم:
همیشه n تا میوتکس رو به یک ترتیب لاک کنید.

اگه این قاعده رعایت شه، از نظر ریسورسی یا همون میوتکسی به مشکلی نمی‌خوریم هرچند که ممکنه طراحیمون هنوز مشکل داشته باشه. دلیل اینکه چرا این روش کار میکنه رو میتونین با کشیدن چیزی به اسم گراف wait-for ببینین.

نکته دومی که میتونه کمک کننده باشه اینه که تا جایی که ممکنه از nested lockها بپرهیزید. این نکته هم میتونه جلوی طراحی‌های مشکل دار رو بگیره.


اما C++ چی در چنته داره؟
خب حداقل دوتا عنصر خوب داریم، کلاس std::scoped_lock که از c++17 پیداش میشه و یک تابع که شاید بشه گفت ورژن قدیمی‌تر این کلاسه، std::lock

اول با std::lock شروع کنیم. کانسپت ساده‌ای داره. این تابع n تا میوتکس رو می‌گیره و به ما قول میده جوری لاکشون کنه که deadlock رخ نده (واقعا نمیدونم چجوری).

و اما std::scoped_lock یه template کلاسه که توی متد سازندش n تا میوتکس رو می‌گیره و جوری لاکشون میکنه که deadlock رخ نده و البته توی دیستراکتورش همه میوتکس‌هایی که بهش داده شده بود رو مثل std::lock_gaurd آنلاک می‌کنه.

به این دلیل این کلاس template classعه که چندین نوع mutex داریم و کلاس انتظار داره تایپ میوتکس‌های ورودی بهش رو بهش بگیم. البته که چون این کلاس توی c++17 اضافه شده و همزمان توی همین ورژن template deduction به استاندارد اضافه شده، میتونین template argument ها رو بهش پاس ندین و بذارین خودش تشخیصشون بده.


توجه کنید که ممکنه لاک کردن میوتکس‌ها با خطا یا exception مواجه بشه که در اینصورت هردوتا عنصر بالا همه میوتکس‌های دیگه‌ای که لاک کردن رو آنلاک می‌کنن و اکسپشن بوجود اومده رو rethrow میکنن.

توی پست بعدی درمورد std::promise و std::future صحبت می‌کنیم.

ادامه

#cpp
#concurrency
#programming
Stuff for Geeks
همزمانی در سی‌پلاس‌پلاس پست ۶ توی این پست میخوام درمورد deadlock بیش‌تر صحبت کنم. اصولا deadlock موقعی پیش میاد که به هر دلیل دوتا ترد به شکل دایره وار منتظر همدیگه بمونن. یکی از دلایل این انتظار میتونه میوتکس(میوتک؟)ها و یا درواقع ریسورس‌ها باشن ولی دلایل…
همزمانی در سی‌پلاس‌پلاس
پست ۷
promise and future

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

داستان از اینجا شروع میشه که قراره چندین thread با هم تعامل داشته باشن. مثلا شما یه برنامه دارید که یه فایل رو از اینترنت دانلود میکنه و این دانلود برنامه این شکلیه که ده تا thread همزمان فایل رو ده تیکه میکنن و دانلودش میکنن و نهایتا وقتی کار تک تکشون تموم شد، ما باید به نحوی بفهمیم و تیکه‌های فایل‌ها رو کنار هم بچینیم تا برسیم به فایل اصلی.

سوال اینه
چجوری بفهمیم چندتا ترد کارشون تموم شده؟

یا کلی‌تر بگیم
چه راه‌های ارتباطی‌ای بین تردها وجود داره؟

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

یکی از معروف‌ترین‌هاش همین future و promise ها هستن.

برای اینکه بهتر بفهمیم اینا چین، مثال زیر رو ببینین:
فرض کنید قراره برای شما یه ایمیل بیاد که براتون خیلی مهمه و میخواین به محض رسیدن ایمیل اون رو ببینین.

دوتا راه پیش رو خواهید داشت. یکی اینکه هر مثلا بیست ثانیه ایمیلتون رو چک کنید ببینین ایمیل جدید اومده یا نه.
یکی دیگه اینکه نوتیف ایمیلتون رو فعال کنید تا وقتی ایمیل رسید، بفهمین.

واضحه که راه دوم بهتره و برای ترد‌ها هم همین داستان رو داریم.
میخوایم وقتی یه ترد منتظر دیتایی از سمت ترد دیگست، به حالت sleep بره نه اینکه توی یه حلقهٔ بینهایت دور بزنه و مدام چک کنه آیا دیتا رسید یا نه.
اینجا دقیقا promise و future ها بکار میان.
البته condition_variable ها هم با تقریب خوبی همین کاربرد رو دارن که بعدتر توضیح میدم.

پس هروقت لازم داشتیم یه ترد از یه ترد دیگه یه دیتایی رو بگیره، یا صرفا بفهمه که اون ترد کارش تموم شد از promise و future استفاده میکنیم.

طریقه استفادش اینطوریه که
۱) یه std::promise می‌سازیم. این کلاس یه template class عه که تایپی که می‌گیره تایپ دیتایی که قراره بعدا توی یه ترد دیگه ازش بگیریم. (اگه قرار نیست دیتایی بگیریم و صرفا قراره بفهمیم کار ترد تموم شده تایپ void بهش میدیم)
۲) آبجکت future رو از promise ساخته شده با تابع get_future استخراج می‌کنیم.
۳) تابع future.wait یا future.get رو صدا می‌زنیم. تابع wait برای حالتیه که دیتایی قرار نیست به ما برگرده و فقط قراره منتظر اتمام کار ترد باشیم و تابع get برای حالتیه که ترد واقعا دیتا بر می‌گردونه. این توابع blocking هستن. یعنی تردی که این توابع رو صدا بزنه، میره به حالت sleep تا موقعی که دیتا آماده بشه.
توابع wait_for و wait_until رو هم داریم که به این صبر کردنه تایم اوت اضافه می‌کنن.

توی لینک زیر می‌تونین مثال ببینین از این کلاس‌ها:
https://en.cppreference.com/w/cpp/thread/future.html

اینجا هم یه ارائه خوب داریم که std::promise و std::future رو پیاده‌سازی میکنه:
https://youtu.be/jfDRgnxDe7o


#cpp
#programming
#concurrency
Storage duration
و
Linkage


فرض کنید یه برنامه c یا cpp داریم که از چندین فایل تشکیل شده

سوال اینه که identifierای که به صورت گلوبال تعریف شده آیا توی همه فایلای دیگه قابل دسترسیه و عملا اون identifier دیگه نمی‌تونه استفاده بشه؟ و اینکه آیا میشه این مشکل(؟) رو رفع کرد؟

برای پاسخ به این سوالات باید با سه تا مفهوم آشنا باشیم
یکیش scope یه متغیره که فک کنم همه آشنا هستیم باهش
اون دوتای دیگه linkage متغیر و storage duration اونه

Storage duration
این خاصیت یه متغیر مشخص میکنه که متغیر کی ساخته میشه و کی ازبین میره. سه تا مدل داریم:
> Dynamic duration
برای متغیرهایی استفاده میشه که توسط برنامه‌نویس تعریف میشه کی از بین بیان و کی از بین برن. درواقع همون متغیرهایی هستن که توی heap قرار میگیرن و با new و free یا delete بوجود میان و از بین میرن

> Static storage duration
متغیرهایی با این duration، از ابتدا تا انتهای برنامه توی مموری وجود دارن

> Automatic duration
این مدل متغیرها اول یه بلوک (یه آکولاد باز و بسته) بوجود میان و آخر اون از بین میرن. مثل متغیرهایی که توی یه تابع تعریف میشن

اما مورد دوم
Linkage
این مورد مشخص می‌کنه که یه identifier در scopeهای مختلف به یک موجود اشاره می‌کنه یا نه. این مورد هم سه مدل داره:
> No linkage
توی این حالت، identifer های مختلف به چیزهای مختلفی اشاره میکنن. مثلا وقتی دوتا متغیر با یک اسم توی دوتا اسکوپ مختلف تعریف میشن، این مدل linkage رو دارن.

> Internal linkage
این مدل linkage مشخص می‌کنه که یه identifier توی یه translation unit مربوط به یک عنصر منحصر به فرده. مثلا اگه یه متغیر گلوبال و static تعریف بشه، توی کل اون فایل identiferاش به اون متغیر اشاره می‌کنه.
درواقع Identifier های
متغیرهای گلوبال و static
توابع استاتیک
متغیرهای گلوبال const
و unnamed namespace ها و عناصر تعریف شده داخلشون
این linkage رو دارن

> External linkage
و مدل آخر linkage، مدل external linkage هست. Identiferای با این مدل linkage در تمام برنامه یکتا خواهد بود و به یک عنصر اشاره خواهد داشت.
موارد زیر identifier هاشون این خاصیت رو داره:
توابع غیر static
متغیرهای گلوبال non-const
متغیرهای گلوبال extern const
متغیرهای گلوبال inline const
و named namespace ها


سورس:
https://www.learncpp.com/cpp-tutorial/scope-duration-and-linkage-summary/

#cpp
#programming