برنامهنویسی چندنخی (Multithreading) و همزمانی در ++C
برنامهنویسی چندنخی به ما این امکان را میدهد که چندین کار را بهطور همزمان در یک برنامه اجرا کنیم. این تکنیک میتواند عملکرد برنامه را بهبود بخشد، بهویژه در سیستمهایی که چندین هسته پردازنده دارند. در ++C، این امکان بهوسیله کتابخانه thread در ++C11 و بعدتر فراهم شده است.
در این بخش، نحوه استفاده از چندنخ و همزمانی (Concurrency) در ++C بررسی میشود.
۱. مفهوم نخ (Thread)
یک نخ (Thread) در حقیقت واحد اجرای یک برنامه است که توسط سیستمعامل مدیریت میشود. هر برنامه میتواند یک یا چند نخ داشته باشد که بهطور همزمان اجرا میشوند.
۱.۱ مزایای برنامهنویسی چندنخی
- افزایش بهرهوری پردازنده: اجرای همزمان چندین نخ میتواند عملکرد برنامه را بر روی سیستمهای چند هستهای بهبود بخشد.
- واحدهای مستقل: کارهایی که مستقل از یکدیگر هستند را میتوان در نخهای جداگانه اجرا کرد.
- بهبود کارایی در برنامههای I/O: زمانی که برنامه منتظر ورودی/خروجی (I/O) است، میتوان از نخهای دیگر برای انجام پردازشهای دیگر استفاده کرد.
۲. استفاده از نخها در ++C
در ++C ورژن 11 و نسخههای بعدی، برای استفاده از نخها میتوان از کلاس std::thread که در کتابخانه <thread> قرار دارد، استفاده کرد.
۲.۱ ایجاد و اجرای نخها
برای ایجاد یک نخ جدید، ابتدا باید یک شیء از کلاس std::thread بسازیم و سپس تابعی را که میخواهیم در آن نخ اجرا شود به آن بدهیم.
#include <thread>
using namespace std;
void print_hello() {
cout << "سلام از نخ!" << endl;
}
int main() {
// ایجاد و اجرای نخ
thread t(print_hello);
// صبر برای اتمام نخ
t.join(); // اطمینان از اینکه نخ تکمیل شده است قبل از ادامه برنامه
return 0;
}
در این مثال، یک نخ جدید ایجاد میشود که تابع print_hello را اجرا میکند. متد ()join برای منتظر ماندن تا پایان اجرای نخ استفاده میشود.
۲.۲ متدهای ()join و ()detach
- ()join: منتظر میماند تا نخ به پایان برسد. اگر از این متد استفاده نشود، برنامه ممکن است قبل از اتمام نخ، خاتمه یابد.
- ()detach: نخ را از نخ اصلی جدا میکند و اجازه میدهد که بهطور مستقل اجرا شود. پس از فراخوانی این متد، نخ دیگر قابل دسترسی نیست و سیستم عامل مدیریت آن را بر عهده میگیرد.
#include <thread>
using namespace std;
void print_hello() {
cout << "سلام از نخ!" << endl;
}
int main() {
// ایجاد و جدا کردن نخ
thread t(print_hello);
t.detach(); // نخ بهطور مستقل اجرا میشود
cout << "نخ اصلی به کار خود ادامه میدهد!" << endl;
return 0;
}
۳. همزمانی (Concurrency) و مشکلات آن
در برنامهنویسی چندنخی، همزمانی به این معناست که چندین نخ بهطور همزمان به دادهها یا منابع مشترک دسترسی دارند. این امر ممکن است باعث بروز مشکلاتی مانند شرایط رقابتی (Race Conditions)، بنبست (Deadlock) و خستگی (Livelock) شود.
۳.۱ شرایط رقابتی (Race Conditions)
زمانی که چندین نخ به یک منبع مشترک دسترسی دارند و در همان زمان سعی میکنند آن را تغییر دهند، ممکن است رفتار پیشبینی نشدهای ایجاد شود.
برای جلوگیری از شرایط رقابتی، باید از قفلها (Locks) استفاده کرد.
۴. استفاده از قفلها (Locks) برای همزمانی
در ++C ورژن 11 و بعدتر، برای مدیریت همزمانی و جلوگیری از شرایط رقابتی، از کلاسهای مختلف قفل مانند std::mutex و std::lock_guard استفاده میشود.
۴.۱ استفاده از std::mutex
#include <thread>
#include <mutex>
using namespace std;
mutex mtx; // قفل برای همزمانی
void print_hello() {
mtx.lock(); // قفل کردن برای جلوگیری از دسترسی همزمان
cout << "سلام از نخ!" << endl;
mtx.unlock(); // آزاد کردن قفل
}
int main() {
thread t1(print_hello);
thread t2(print_hello);
t1.join();
t2.join();
return 0;
}
در این مثال، دو نخ بهطور همزمان تلاش میکنند که پیامی را چاپ کنند، اما با استفاده از mutex دسترسی به بخش مشترک (چاپ) تنها برای یکی از نخها در هر لحظه ممکن است.
۴.۲ استفاده از std::lock_guard
برای سادهتر کردن مدیریت قفلها، میتوان از std::lock_guard استفاده کرد. این کلاس بهطور خودکار قفل را در هنگام ورود به بخش کد میگیرد و هنگام خروج از آن بهطور خودکار قفل را آزاد میکند.
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
void print_hello() {
lock_guard<mutex> guard(mtx); // قفل بهصورت خودکار گرفته میشود
cout << "سلام از نخ!" << endl;
}
int main() {
thread t1(print_hello);
thread t2(print_hello);
t1.join();
t2.join();
return 0;
}
۵. بنبست (Deadlock) و نحوه جلوگیری از آن
بنبست زمانی رخ میدهد که دو یا چند نخ منتظر یکدیگر برای آزاد کردن قفلها باشند و هیچکدام قادر به ادامه نباشند. برای جلوگیری از بنبست، باید دقت کنید که قفلها همیشه در یک ترتیب مشخص گرفته شوند.
۵.۱ جلوگیری از بنبست
یکی از راههای جلوگیری از بنبست استفاده از ()std::lock است که به شما اجازه میدهد چندین قفل را بهطور همزمان و بدون خطر بنبست بگیرید.
#include <thread>
#include <mutex>
using namespace std;
mutex mtx1, mtx2;
void deadlock_example() {
lock(mtx1, mtx2); // قفل کردن هر دو mutex بدون خطر بنبست
lock_guard<mutex> lg1(mtx1, adopt_lock);
lock_guard<mutex> lg2(mtx2, adopt_lock);
cout << "هیچ بنبستی رخ نداده است!" << endl;
}
int main() {
thread t1(deadlock_example);
thread t2(deadlock_example);
t1.join();
t2.join();
return 0;
}
در این مثال، از ()std::lock استفاده شده است که هر دو قفل را بهطور همزمان میگیرد و از بروز بنبست جلوگیری میکند.
۶. نتیجهگیری
برنامهنویسی چندنخی در ++C میتواند به شما کمک کند که از تمام توان پردازندههای چند هستهای بهرهبرداری کنید و عملکرد برنامه را بهبود بخشید. با این حال، همزمانی نخها میتواند مشکلاتی مانند شرایط رقابتی و بنبست را ایجاد کند. استفاده از قفلها و ابزارهای مناسب برای همزمانی مانند std::mutex و std::lock_guard میتواند به شما کمک کند تا از این مشکلات جلوگیری کنید و برنامههایی ایمن و کارآمد بنویسید.
