push_back(c++11)?
emplace_back(c++17)?
مسئله اینست
فرض کنید که یک کلاس با تعداد زیادی فیلد(سایز زیاد) داریم و میخوایم یه وکتور از این تایپ داشته باشیم:
خب حالا چجوری داخل یه وکتور اشیائی ازین کلاس رو وارد کنیم؟
آیا پوش بک بهینست؟
برای جواب به این سوال، فرض کنید یه شیئ ساخته شده از کلاس حجیممون داریم. مثلا اسمش 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
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
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++ ای خوب باهش آشنا باشه از کپی شدن های اضافه جلوگیری میکنه.
مثلا فرض کنید یه تابع داریم که از یه کلاس دلخواهی یه آبجکت میسازه و برمیگردونه:
خب اگه یکم دقیق به این کد نگاه کنیم، میبینیم که توی تابع یه شیئ ساخته میشه(صدا زده شدن کانستراکتور) و بعد کپی کانستراکتور صدا زده میشه و آبجکت داخل مین ساخته میشه. پس اینجا آبجکت داخل تابع ساخته شد، کپی شد و از بین رفت که خوب نیست چون کپی شدن الکی و اضافی داریم.
برای حل کردن این مشکل از 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
یا
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
Stuff for Geeks
Return value optimization یا RVO در سیپلاسپلاس این قابلیت که واجبه هر برنامه نویس C++ ای خوب باهش آشنا باشه از کپی شدن های اضافه جلوگیری میکنه. مثلا فرض کنید یه تابع داریم که از یه کلاس دلخواهی یه آبجکت میسازه و برمیگردونه: SomeType myFunction(){ SomeType…
YouTube
C++ RVO: Return Value Optimization for Performance in Bloomberg C++ Codebases - Michelle Fae D'Souza
https://cppcon.org
---
Can You RVO? Using Return Value Optimization for Performance in Bloomberg C++ Codebases - Michelle Fae D'Souza - CppCon 2024
---
Learn what Return Value Optimization (RVO) is, and what you can do to ensure the compiler applies it…
---
Can You RVO? Using Return Value Optimization for Performance in Bloomberg C++ Codebases - Michelle Fae D'Souza - CppCon 2024
---
Learn what Return Value Optimization (RVO) is, and what you can do to ensure the compiler applies it…
CRTP in C++
فرض کنید که یه کلاس بیس داریم و چندین کلاس که ازین کلاس ارث بری کردن.
فرض کنید توی کلاس بیس یه تابع virtual و چندتا تابع عادی داشته باشیم و داخل یکی ازین توابع عادی بخواهیم این تابع virtual رو صدا بزنیم. مشکلی که اینجا خواهیم داشت، اینه که صدا زدن تابع virtual با this اتفاق خواهد افتاد و نتیجتا تابع virtual کلاس بیس صدا زده خواهد شد و نه تابع کلاس های ارث بری کرده.
خب حالا فرض کنید لازم داریم که توی کلاس والد، توابع virtual کلاسهای فرزند رو صدا بزنیم. چه کنیم؟
یا فرض کنید درکلاس های فرزند یک تابع استاتیک وجود دارد که پیاده سازی آن توسط کلاس فرزند انجام گرفته است. برای صدا زدن این توابع درون تابع والد باید چه کرد؟
اینجاست که CRTP وارد بحث میشه. توی این پترن، میایم تایپ کلاس های فرزند رو درقالب تمپلت به کلاس بیس پاس میدیم:
اینجوری دیگه توی کلاس بیس میدونم تایپ کلاس فرزند چیه و میتونیم پوینترthis رو بهش کست کنیم و از توابع virtualاش استفاده کنیم یا حتی توابع استاتیکش رو صدا بزنیم.
به این پترن میگن CRTP
#cpp
#programming
فرض کنید که یه کلاس بیس داریم و چندین کلاس که ازین کلاس ارث بری کردن.
فرض کنید توی کلاس بیس یه تابع 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
با این الگوریتم میتونین کوچیک ترین عضو یه کانتینر رو پیدا کنین
و البته برای مقایسه المنتای کانتینر میتونین یه لامبدا بهش پاس بدین:
توجه کنید که این تابع ایترتور برمیگردونه و برای همینه که از * استفاده شده.
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
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
پست ۶
توی این پست میخوام درمورد 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
بردیا توی کانالش یه آموزش Qt/QML فارسی خیلی خوب گذاشته:
https://youtube.com/playlist?list=PLuU5PicIfhjhVeRfN-wum_LFUPq3J0w-e
#cpp
#qt
#programming
https://youtube.com/playlist?list=PLuU5PicIfhjhVeRfN-wum_LFUPq3J0w-e
#cpp
#qt
#programming
YouTube
آموزش فریمورک Qt6
در این پلیلیست قراره فریمورک Qt رو زیر و رو کنیم.
❤1
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
پست ۷
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
و
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