جاوا

جلسه ۷۴: تفاوت برنامه و پروسه و نخ در جاوا

۱٫ int counter = 0;
۲٫
۳٫ void incrementCounter() {
۴٫   counter++;
۵٫ }
دستور افزایش متغیر counter در خط ۴ به مراحلی مشابه مراحل زیر در کامپیوتر تجزیه می شود:
  • مقدار متغیر counter را از رجیستری (حافظه موقتی سریع) که در آن ذخیره شده است بخوان
  • یک واحد به مقداری که خوانده شده است اضافه کن
  • نتیجه محاسبات فوق را دوباره در رجیستر ذخیره کن
پس دستور خط ۴ که فقط یک دستور به نظر می رسد در حقیقت یک روند سه مرحله ای است! حال تصور کنید اگر دو نخ بخواهند همین تابع incrementCounter در قطعه کد بالا را اجرا کنند ، یکی از راه های محتمل برای اجرای دو نخ به شرح زیر است: یکی از نخ ها را T1 و دیگری را T2 می نامیم. فرض کنید مقدار شمارنده برابر با ۷ باشد.
  1. اجرای T1 در حال حاضر روی CPU برنامه ریزی شده و وارد تابع می شود. مرحله A را انجام می دهد یعنی مقدار متغیر را از رجیستر می خواند که ۷ است.
  2. سیستم عامل تصمیم می گیرد پروسه اجرایی را تغییر دهد و T2 را وارد مرحله اجرایی کرده و T1 را از اجرا خارج کند.
  3. T2 وارد فاز اجرا می شود و از شانس خوبش قبل از تحویل فاز اجرایی به T1 تمام سه مرحله B ، A و C را انجام می دهد. مقدار ۷ را می خواند ، یک عدد به آن اضافه می کند و عدد ۸ را ذخیره می کند.
  4. T1 دوباره برمی گردد و از آنجا که وضعیت آن توسط سیستم عامل ذخیره شده است ، هنوز هم مقدار قدیمی متغیر counter یعنی ۷ را استفاده می کند این مقداری است که قبل از تغییر پروسه خوانده شده بود. نخ T1 نمی داند که پشت سرش مقدار متغیر به روز شده است. متأسفانه فکر می کند که مقدار هنوز ۷ است ، یک عدد به آن اضافه می کند و حاصلجمع حاصل یعنی ۸ را بجای مقدار counter (که از قبل ۸ بود) می نویسد. اگر نخ ها به صورت سریالی پشت سر هم اجرا می شدند، حالا مقدار نهایی ۹ بود.
مشکلات باید برای خواننده زیرک آشکار باشد. بدون محافظت صحیح در دسترسی به متغیرها یا ساختارهای داده ای قابل تغییر ، نخ ها می توانند خطاهایی ایجاد کنند که به سختی قابل یافتن باشد. از آنجا که ترتیب اجرای نخ ها قابل پیش بینی هم نیست و کاملاً به سیستم عامل بستگی دارد ، بنابراین نمی توان درمورد نحوه برنامه ریزی ترتیب و اجرای نخ ها ، فرضی داشت. پس نتیجه حاصل از اجرای برنامه همگام سازی نشده قبل از اجرا نامشخص است.

کلاس Thread unsafe

یک دقیقه وقت بگذارید و برنامه زیر را بررسی کنید. این برنامه یک شمارنده را به تعداد دفعات یکسان افزایش و کاهش می دهد. مقدار نهایی شمارنده باید صفر باشد ، با این حال ، اگر برنامه را به تعداد بار کافی اجرا کنید ، گاهی اوقات نتیجه مورد انتظار صفر را دریافت می کنید و در برخی دیگر از مواقع ، مقدار غیر صفر را دریافت می کنید. در این برنامه نخ ها را به مدتی تصادفی sleep می کنیم یا می خوابانیم ، تا امکانی به نخ ها بدهیم که به ترتیبی غیر قطعی اجرا شوند.
import java.util.Random;

class DemoThreadUnsafe {

    // We'll use this to randomly sleep our threads
    static Random random = new Random(System.currentTimeMillis());

    public static void main(String args[]) throws InterruptedException {

        // create object of unsafe counter
        ThreadUnsafeCounter badCounter = new ThreadUnsafeCounter();

        // setup thread1 to increment the badCounter 200 times
        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    badCounter.increment();
                    DemoThreadUnsafe.sleepRandomlyForLessThan10Secs();
                }
            }
        });

        // setup thread2 to decrement the badCounter 200 times
        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    badCounter.decrement();
                    DemoThreadUnsafe.sleepRandomlyForLessThan10Secs();
                }
            }
        });

        // run both threads
        thread1.start();
        thread2.start();

        // wait for t1 and t2 to complete.
        thread1.join();
        thread2.join();

        // print final value of counter
        badCounter.printFinalCounterValue();
    }

    public static void sleepRandomlyForLessThan10Secs() {
        try {
            Thread.sleep(random.nextInt(10));
        } catch (InterruptedException ie) {
        }
    }
}

class ThreadUnsafeCounter {

    int count = 0;

    public void increment() {
        count++;
    }

    public void decrement() {
        count--;
    }

    void printFinalCounterValue() {
        System.out.println("counter is: " + count);
    }
}
این جلسه تفاوت بین برنامه ، پروسه و نخ را بررسی می کند. همچنین نمونه ای از یک برنامه thread-unsafe (چند نخی ناایمن) را می بینید. موارد زیر را بیان خواهیم کرد:
  • برنامه
  • پروسه
  • نخ
  • هشدارها
  • برنامه شمارنده
  • کلاس Thread unsafe

برنامه

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

پروسه

پروسه یک برنامه در حال اجرا است. پروسه یک محیط اجرا است که شامل دستورالعمل ها ، داده های کاربر و بخش داده های سیستم و همچنین بسیاری از منابع دیگر مانند CPU ، حافظه ، فضای آدرس ، دیسک و I/O شبکه است که در زمان اجرا اختصاص می یابد. چندین پروسه از یک برنامه می تواند به صورت همزمان درحال اجرا باشد اما یک پروسه لزوما فقط به یک برنامه تعلق دارد.

نخ

نخ (Thread) کوچکترین واحد اجرا در یک پروسه (process) است. یک نخ به سادگی دستورات را به طور متوالی اجرا می کند. هر پروسه می تواند چندین نخ داشته باشد که به عنوان بخشی از آن پروسه اجرا می شوند. معمولاً برخی از حالتها یا متغیرهای مرتبط با پروسه وجود دارد که با تمام نخ ها به اشتراک گذاشته می شود بعلاوه هر نخ می تواند برخی از متغیرها یا حالتهای خصوصی داشته باشد. متغیرهای مشترک بین نخ های یک پروسه برای همه نخ های آن پروسه قابل مشاهده و در دسترس است و هنگامی که هر نخی این متغیر مشترک را بخواند یا تغییردهد ، باید دقت شود. چندین ساختار توسط زبانهای برنامه نویسی مختلف برای محافظت و نظم در دسترسی به این متغیرهای مشترک ارائه شده است که در جلسه های آینده بیشتر به آنها خواهیم پرداخت.

اصطلاح ها

توجه داشته باشید که اصطلاح برنامه و پروسه اغلب به جای هم استفاده می شوند اما بیشتر اوقات منظور پروسه است. همچنین مفهوم سیستم های “پردازش موازی” وجود دارد که در آن چندین پروسه در بیش از یک CPU پردازش می شوند. معمولاً این امر به پشتیبانی سخت افزاری نیاز دارد که یک سیستم واحد دارای چندین هسته پردازنده باشد یا اجرای برنامه در گروهی از ماشین ها انجام شود. پروسه ها هیچ منابعی را بین خود به اشتراک نمی گذارند در حالیکه نخ های یک پروسه می توانند منابع اختصاص یافته به آن پروسه خاص ، از جمله فضای آدرس حافظه را به اشتراک بگذارند. با این حال ، زبانها امکاناتی را فراهم می کنند تا ارتباط بین پروسه ها را امکان پذیر سازند.

برنامه شمارنده

در زیر مثالی آورده شده است که نشان می دهد که هنگام تغییر داده مشترک توسط چند نخ ، احتیاط لازم است. درصورت همگام سازی نادرست بین نخ ها بسته به اینکه نخ ها به چه ترتیبی اجرا می شوند ، خروجی برنامه را متفاوت می کند. قطعه کد زیر را در نظر بگیرید:
۱٫ int counter = 0;
۲٫
۳٫ void incrementCounter() {
۴٫   counter++;
۵٫ }
دستور افزایش متغیر counter در خط ۴ به مراحلی مشابه مراحل زیر در کامپیوتر تجزیه می شود:
  • مقدار متغیر counter را از رجیستری (حافظه موقتی سریع) که در آن ذخیره شده است بخوان
  • یک واحد به مقداری که خوانده شده است اضافه کن
  • نتیجه محاسبات فوق را دوباره در رجیستر ذخیره کن
پس دستور خط ۴ که فقط یک دستور به نظر می رسد در حقیقت یک روند سه مرحله ای است! حال تصور کنید اگر دو نخ بخواهند همین تابع incrementCounter در قطعه کد بالا را اجرا کنند ، یکی از راه های محتمل برای اجرای دو نخ به شرح زیر است: یکی از نخ ها را T1 و دیگری را T2 می نامیم. فرض کنید مقدار شمارنده برابر با ۷ باشد.
  1. اجرای T1 در حال حاضر روی CPU برنامه ریزی شده و وارد تابع می شود. مرحله A را انجام می دهد یعنی مقدار متغیر را از رجیستر می خواند که ۷ است.
  2. سیستم عامل تصمیم می گیرد پروسه اجرایی را تغییر دهد و T2 را وارد مرحله اجرایی کرده و T1 را از اجرا خارج کند.
  3. T2 وارد فاز اجرا می شود و از شانس خوبش قبل از تحویل فاز اجرایی به T1 تمام سه مرحله B ، A و C را انجام می دهد. مقدار ۷ را می خواند ، یک عدد به آن اضافه می کند و عدد ۸ را ذخیره می کند.
  4. T1 دوباره برمی گردد و از آنجا که وضعیت آن توسط سیستم عامل ذخیره شده است ، هنوز هم مقدار قدیمی متغیر counter یعنی ۷ را استفاده می کند این مقداری است که قبل از تغییر پروسه خوانده شده بود. نخ T1 نمی داند که پشت سرش مقدار متغیر به روز شده است. متأسفانه فکر می کند که مقدار هنوز ۷ است ، یک عدد به آن اضافه می کند و حاصلجمع حاصل یعنی ۸ را بجای مقدار counter (که از قبل ۸ بود) می نویسد. اگر نخ ها به صورت سریالی پشت سر هم اجرا می شدند، حالا مقدار نهایی ۹ بود.
مشکلات باید برای خواننده زیرک آشکار باشد. بدون محافظت صحیح در دسترسی به متغیرها یا ساختارهای داده ای قابل تغییر ، نخ ها می توانند خطاهایی ایجاد کنند که به سختی قابل یافتن باشد. از آنجا که ترتیب اجرای نخ ها قابل پیش بینی هم نیست و کاملاً به سیستم عامل بستگی دارد ، بنابراین نمی توان درمورد نحوه برنامه ریزی ترتیب و اجرای نخ ها ، فرضی داشت. پس نتیجه حاصل از اجرای برنامه همگام سازی نشده قبل از اجرا نامشخص است.

کلاس Thread unsafe

یک دقیقه وقت بگذارید و برنامه زیر را بررسی کنید. این برنامه یک شمارنده را به تعداد دفعات یکسان افزایش و کاهش می دهد. مقدار نهایی شمارنده باید صفر باشد ، با این حال ، اگر برنامه را به تعداد بار کافی اجرا کنید ، گاهی اوقات نتیجه مورد انتظار صفر را دریافت می کنید و در برخی دیگر از مواقع ، مقدار غیر صفر را دریافت می کنید. در این برنامه نخ ها را به مدتی تصادفی sleep می کنیم یا می خوابانیم ، تا امکانی به نخ ها بدهیم که به ترتیبی غیر قطعی اجرا شوند.
import java.util.Random;

class DemoThreadUnsafe {

    // We'll use this to randomly sleep our threads
    static Random random = new Random(System.currentTimeMillis());

    public static void main(String args[]) throws InterruptedException {

        // create object of unsafe counter
        ThreadUnsafeCounter badCounter = new ThreadUnsafeCounter();

        // setup thread1 to increment the badCounter 200 times
        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    badCounter.increment();
                    DemoThreadUnsafe.sleepRandomlyForLessThan10Secs();
                }
            }
        });

        // setup thread2 to decrement the badCounter 200 times
        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    badCounter.decrement();
                    DemoThreadUnsafe.sleepRandomlyForLessThan10Secs();
                }
            }
        });

        // run both threads
        thread1.start();
        thread2.start();

        // wait for t1 and t2 to complete.
        thread1.join();
        thread2.join();

        // print final value of counter
        badCounter.printFinalCounterValue();
    }

    public static void sleepRandomlyForLessThan10Secs() {
        try {
            Thread.sleep(random.nextInt(10));
        } catch (InterruptedException ie) {
        }
    }
}

class ThreadUnsafeCounter {

    int count = 0;

    public void increment() {
        count++;
    }

    public void decrement() {
        count--;
    }

    void printFinalCounterValue() {
        System.out.println("counter is: " + count);
    }
}

نوشته های مشابه

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

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

دکمه بازگشت به بالا