چند نخی یا Multithreading در ++C

چند نخی شکل ویژه‌ای از چند وظیفه‌ای (multitasking) است. چند وظیفه‌ای خاصیتی است که به کامپیوتر این امکان را می‌دهد تا دو یا چند برنامه را به طور همزمان اجرا کند. در حالت کلی، دو نوع چند وظیفه‌ای وجود دارد: مبتنی بر پروسس‌ (process-based) و مبتنی بر نخ (thread-based).

چند وظیفه‌ای مبتنی بر پروسس، اجرای همزمان برنامه‌ها را برعهده می‌گیرد. چند وظیفه‌ای مبتنی بر نخ اجرای همزمان چند تکه از یک برنامه را برعهده می‌گیرد.

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

++C هیچ پشتیبانی داخلی برای برنامه‌های چند نخی فراهم نکرده است. در عوض، این برعهده سیستم عامل است که چنین ویژگی را به طور کامل فراهم کند.

این آموزش فرض را براین می‌گذارد که شما از سیستم عامل لینوکس استفاده می‌کنید. قصد داریم که برنامه چند نخی ++C را با استفاده از POSIX بنویسیم. POSIX Threads یا API ،Pthreads برای چند نخی را فراهم می‌آورد، این API روی بسیاری از سیستم‌های POSIX شبه یونیکس مانند FreeBSD ،NetBSD ،GNU/Linux ،Mac OS X و Solaris وجود دارد.

ساخت نخ‌ها

از روتین زیر برای ساخت یک نخ POSIX استفاده می‌شود.

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) 

در اینجا، «pthread_create» یک نخ جدید ساخته و آن را قابل اجرا می‌سازد. این روتین را می‌توان هر چند بار که نیاز باشد در برنامه فراخوانی کنید. توضیح این پارامترها در زیر آمده است.

ردیف

پارامتر و توضیح آن

1

thread

یک شناسه یکتا برای نخ جدیدی که روتین برمی‌گرداند.

2

attr

یک شی attribute ناشفاف که برای تنظیم خواص نخ استفاده می‌شود. می‌توان یک شی attribute برای نخ مشخص کرد، یا برای حالت تنظیمات پیش فرض NULL را قرار داد.

3

start_routine

روتین ++C که نخ پس از ساخته شدن باید آن را اجرا کند.

4

arg

یک آرگومان منفرد که می‌توان به start_routine ارسال کرد. این آرگومان باید  به صورت یک رفرنس به یک اشاره‌گر void ارسال شود. اگر نخواهیم این آرگومان را ارسال کنیم، مقدار NULL وارد می‌کنیم.

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

خاتمه دادن به یک نخ

روتین زیر برای خاتمه دادن به یک نخ POSIX استفاده می‌شود.

#include <pthread.h>
pthread_exit (status)

در اینجا «pthread_exit» برای خروج از یک نخ به صورت صریح به کار می‌رود. عموماً روتین «pthread_exit» را پس از اینکه یک نخ کارش را به طور کامل انجام داد و دیگر به وجودش نیازی نبود، فراخوانی می‌کنند.

اگر تابع ()main قبل از نخ‌هایی که خودش ساخته به اتمام برسد و با روتین ()pthread_exit خارج شود، دیگر نخ‌ها به اجرای خود ادامه خواهند داد. درغیر این صورت، آنها نیز با تمام شدن ()main خاتمه می‌یابند.

مثال

این مثال ساده با استفاده از روتین «pthread_create» پنج نخ ایجاد می‌کند. هر نخ یک پیام «Hello World» چاپ کرده و با فراخوانی ()pthread_exit خاتمه می‌یابد.

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

void *PrintHello(void *threadid) {
   long tid;
   tid = (long)threadid;
   cout << "Hello World! Thread ID, " << tid << endl;
   pthread_exit(NULL);
}

int main () {
   pthread_t threads[NUM_THREADS];
   int rc;
   int i;
   
   for( i = 0; i < NUM_THREADS; i++ ) {
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
      
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

کد بالا را با کتابخانه lpthread– به صورت زیر کامپایل کنید.

$gcc test.cpp -lpthread

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

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread ID, 4

ارسال آرگومان به نخ‌ها

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

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

struct thread_data {
   int  thread_id;
   char *message;
};

void *PrintHello(void *threadarg) {
   struct thread_data *my_data;
   my_data = (struct thread_data *) threadarg;

   cout << "Thread ID : " << my_data->thread_id ;
   cout << " Message : " << my_data->message << endl;

   pthread_exit(NULL);
}

int main () {
   pthread_t threads[NUM_THREADS];
   struct thread_data td[NUM_THREADS];
   int rc;
   int i;

   for( i = 0; i < NUM_THREADS; i++ ) {
      cout <<"main() : creating thread, " << i << endl;
      td[i].thread_id = i;
      td[i].message = "This is message";
      rc = pthread_create(&threads[i], NULL, PrintHello, (void *)&td[i]);
      
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

با کامپایل و اجرای کد فوق، خروجی زیر حاصل می‌شود.

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Thread ID : 3 Message : This is message
Thread ID : 2 Message : This is message
Thread ID : 0 Message : This is message
Thread ID : 1 Message : This is message
Thread ID : 4 Message : This is message

نخ‌های قابل اتصال و منفصل

روتین‌های زیر برای متصل کردن (Join) و منفصل کردن (detach) نخ‌ها به کار می‌روند.

pthread_join (threadid, status) 
pthread_detach (threadid)

زیرروتین ()pthread_join نخ فراخواننده را تا زمانی‌که نخ threadid خاتمه یابد، مسدود می‌کند. وقتی که نخ ساخته می‌شود، یکی از ویژگی‌های آن قابلیت اتصال (joinable) یا قابلیت انفصال (detached) می‌باشد. تنها نخ‌هایی که به صورت قابل اتصال تعریف می‌شوند قابلیت اتصال خواهند داشت. اگر یک نخ به صورت منفصل ساخته شود، دیگر قابل اتصال نخواهد بود.

این مثال نشان می‌دهد که چگونه با استفاده از روتین «Pthread join» می‌توان برای تکمیل شدن یک نخ منتظر ماند.

#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>

using namespace std;

#define NUM_THREADS 5

void *wait(void *t) {
   int i;
   long tid;

   tid = (long)t;

   sleep(1);
   cout << "Sleeping in thread " << endl;
   cout << "Thread with id : " << tid << "  ...exiting " << endl;
   pthread_exit(NULL);
}

int main () {
   int rc;
   int i;
   pthread_t threads[NUM_THREADS];
   pthread_attr_t attr;
   void *status;

   // Initialize and set thread joinable
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

   for( i = 0; i < NUM_THREADS; i++ ) {
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], &attr, wait, (void *)i );
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }

   // free attribute and wait for the other threads
   pthread_attr_destroy(&attr);
   for( i = 0; i < NUM_THREADS; i++ ) {
      rc = pthread_join(threads[i], &status);
      if (rc) {
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
      cout << "Main: completed thread id :" << i ;
      cout << "  exiting with status :" << status << endl;
   }

   cout << "Main: program exiting." << endl;
   pthread_exit(NULL);
}

با کامپایل و اجرای کد فوق، خروجی زیر حاصل می‌شود.

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread
Thread with id : 0 .... exiting
Sleeping in thread
Thread with id : 1 .... exiting
Sleeping in thread
Thread with id : 2 .... exiting
Sleeping in thread
Thread with id : 3 .... exiting
Sleeping in thread
Thread with id : 4 .... exiting
Main: completed thread id :0  exiting with status :0
Main: completed thread id :1  exiting with status :0
Main: completed thread id :2  exiting with status :0
Main: completed thread id :3  exiting with status :0
Main: completed thread id :4  exiting with status :0
Main: program exiting.

منبع: ترجمه از سایت  tutorialspoint.com

منبع: عکس شاخص از سایت medium.com

اگر علاقمند بودید در ادامه توصیه می‌کنم مقاله آموزش مولتی ‌تسکینگ در آردوینو – چگونه از دستور ()millis در کدهای آردوینو استفاده کنیم؟ را هم مطالعه کنید. همچنین بقیه جلسات آموزش ++C  را هم دنبال کنید.

اگر این نوشته‌ برایتان مفید بود لطفا کامنت بنویسید.

مطالعه دیگر جلسات این آموزش<< جلسه قبلی                    جلسه بعدی >>
مطلب پیشنهادی:  اورلود کردن عملگرهای یگانی در ++C

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

یک دیدگاه

  1. در کد اول مقدار i=0 جا افتاده است