سیستم های چند رشته ای به چه منظور مورد استفاده قرار می گیرند. هشت قانون ساده برای توسعه برنامه های چند رشته ای

چه موضوعی بیشترین س questionsالات و مشکلات را برای مبتدیان ایجاد می کند؟ وقتی از معلم و برنامه نویس جاوا الکساندر پریاخین در این مورد سوال کردم ، او بلافاصله پاسخ داد: "چند رشته ای". با تشکر از او برای ایده و کمک در تهیه این مقاله!

ما دنیای داخلی برنامه و فرآیندهای آن را بررسی می کنیم ، می فهمیم که اصل چند منظوره چیست ، چه زمانی مفید است و چگونه آن را پیاده سازی می کنیم - با استفاده از جاوا به عنوان مثال. اگر در حال یادگیری زبان OOP متفاوت هستید ، نگران نباشید: اصول اساسی یکسان هستند.

درباره جریانها و منشأ آنها

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

احتمالاً با "Task Manager" ویندوز (در لینوکس "System Monitor") برخورد کرده اید و می دانید که فرآیندهای در حال اجرا غیر ضروری سیستم را بارگیری می کند و "سنگین ترین" آنها اغلب منجمد می شود ، بنابراین باید به اجبار خاتمه یابد. به

اما کاربران عاشق چند وظیفه ای هستند: به آنها نان ندهید - اجازه دهید ده ها پنجره را باز کنند و به جلو و عقب بپرند. یک معضل وجود دارد: شما باید از عملکرد همزمان برنامه ها اطمینان حاصل کنید و در عین حال بار سیستم را کاهش دهید تا سرعت آن کاهش نیابد. فرض کنید سخت افزار نمی تواند نیازهای مالکان را برآورده کند - شما باید مشکل را در سطح نرم افزار حل کنید.

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

اگر یک مورد پیچیده را به چند مورد ساده تقسیم کنید ، راحت تر می توانید به آن نزدیک شوید. بنابراین هنگام کار با حافظه: یک فرایند "سنگین" به موضوعاتی تقسیم می شود که منابع کمتری را اشغال می کنند و به احتمال زیاد کد را به ماشین حساب تحویل می دهند (دقیقاً چگونه - در زیر مشاهده کنید).

هر برنامه حداقل یک فرایند دارد و هر فرایند حداقل یک رشته دارد که به آن نخ اصلی گفته می شود و در صورت لزوم موارد جدیدی از آن راه اندازی می شود.

تفاوت بین رشته ها و فرآیندها

    نخ ها از حافظه اختصاص داده شده برای فرایند استفاده می کنند و فرآیندها به فضای حافظه خاص خود نیاز دارند. بنابراین ، رشته ها سریعتر ایجاد و تکمیل می شوند: سیستم نیازی ندارد هر بار یک فضای آدرس جدید به آنها اختصاص دهد و سپس آن را آزاد کند.

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

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

اما چرا یک برنامه محبوب مانند Firefox مسیر ایجاد چندین فرآیند را طی می کند؟ زیرا برای مرورگر است که برگه های جداگانه کار می کنند قابل اعتماد و انعطاف پذیر است. اگر مشکلی در یک فرآیند وجود داشته باشد ، لازم نیست کل برنامه را خاتمه دهید - می توانید حداقل بخشی از داده ها را ذخیره کنید.

چند رشته ای چیست

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

بار محاسباتی بین دو یا چند هسته توزیع می شود ، به طوری که رابط و سایر اجزای برنامه کار یکدیگر را کند نمی کند.

برنامه های چند رشته ای را می توان روی پردازنده های تک هسته ای اجرا کرد ، اما سپس نخ ها به نوبه خود اجرا می شوند: اولین مورد کار کرد ، حالت آن ذخیره شد - دومی مجاز به کار بود ، ذخیره شد - به اولین بازگردانده شد یا سوم راه اندازی شد ، و غیره.

افراد شلوغ شکایت دارند که فقط دو دست دارند. فرآیندها و برنامه ها می توانند تا آنجا که لازم است دست های زیادی برای تکمیل کار در اسرع وقت داشته باشند.

منتظر سیگنال باشید: همگام سازی در برنامه های چند رشته ای

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

ابزارهای اصلی همگام سازی

طرد متقابل (محرومیت متقابل ، مخفف - mutex) - "پرچم" به موضوعی که در حال حاضر مجاز به کار با منابع مشترک است ، می رود. دسترسی نخ های دیگر به منطقه حافظه اشغال شده را حذف می کند. چندین mutexes در یک برنامه وجود دارد و می توانند بین فرآیندها به اشتراک گذاشته شوند. یک مشکل وجود دارد: mutex برنامه را مجبور می کند هر بار به هسته سیستم عامل دسترسی پیدا کند ، که گران است.

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

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

ناحیه ی بحرانی - یک مکانیسم پیچیده تر که یک شمارنده حلقه و یک سمفور را ترکیب می کند. شمارنده به شما امکان می دهد شروع سمافور را برای زمان مورد نظر به تعویق بیندازید. مزیت آن این است که هسته تنها در صورتی فعال می شود که بخش مشغول باشد و نیاز به روشن شدن سمافور باشد. بقیه زمان موضوع در حالت کاربر اجرا می شود. افسوس که یک بخش فقط در یک فرآیند قابل استفاده است.

نحوه پیاده سازی چند رشته ای در جاوا

کلاس Thread وظیفه کار با نخ ها در جاوا را بر عهده دارد. ایجاد یک موضوع جدید برای اجرای یک کار به معنی ایجاد یک نمونه از کلاس Thread و مرتبط کردن آن با کد مورد نظر شما است. این میتواند با دو راه انجام شود:

    موضوع فرعی موضوع؛

    رابط Runnable را در کلاس خود پیاده سازی کنید و سپس نمونه های کلاس را به سازنده Thread منتقل کنید.

در حالی که ما به موضوع بن بست ها (بن بست ها) نمی پردازیم ، وقتی نخ ها کار یکدیگر را مسدود کرده و آویزان می شوند ، آن را برای مقاله بعدی می گذاریم.

مثال چند رشته ای جاوا: پینگ پنگ با mutexes

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

ابتدا بیایید یک کلاس ایجاد کنیم که ویژگی های Thread را که قبلاً می شناسیم به ارث برده باشد و یک متد kickBall بنویسیم:

کلاس عمومی PingPongThread موضوع را گسترش می دهد (PingPongThread (نام رشته) (this.setName (نام) ؛ // نام موضوع را نادیده بگیرید)Override public void run () (توپ توپ = Ball.getBall () ؛ در حالی که (ball.isInGame () ) (kickBall (توپ) ؛)) خصوصی خالی kickBall (توپ توپ) (اگر (! ball.getSide (). مساوی (getName ())) (ball.kick (getName ())؛)))

حالا بیایید مراقب توپ باشیم. او با ما ساده نخواهد بود ، اما به یاد ماندنی است: تا بتواند بگوید چه کسی ، از کدام طرف و چند بار به او ضربه زده است. برای انجام این کار ، از mutex استفاده می کنیم: اطلاعات مربوط به کار هر یک از نخ ها را جمع آوری می کند - این امر به نخ های جدا شده اجازه می دهد تا با یکدیگر ارتباط برقرار کنند. پس از ضربه پانزدهم ، ما توپ را از بازی خارج می کنیم ، تا آسیبی جدی به آن وارد نشود.

کلاس عمومی توپ (ضربه خصوصی int = 0 ؛ نمونه خصوصی استاتیک توپ = جدید توپ () ؛ خصوصی رشته خصوصی = "" ؛ توپ خصوصی () () استاتیک توپ getBall () (نمونه بازگشت ؛) ضربه خلأ همزمان (نام پخش رشته) (ضربات ++ ؛ side = نام بازی ؛ System.out.println (ضربات + "" + سمت) ؛) String getSide () (طرف برگشت ؛) boolean isInGame () (بازگشت (ضربات< 15); } }

و اکنون دو رشته بازیکن وارد صحنه می شوند. بیایید آنها را بدون هیچ گونه صحبت دیگر ، پینگ و پنگ صدا کنیم:

کلاس عمومی PingPongGame (PingPongThread player1 = جدید PingPongThread ("پینگ") ؛ PingPongThread player2 = جدید PingPongThread ("پنگ") ؛ توپ توپ ؛ PingPongGame () (توپ = Ball.getBall () ؛) void startGame () InterruptException را پرتاب می کند ( .start ()؛ player2.start ()؛))

"ورزشگاه کامل مردم - زمان شروع مسابقه است." ما به طور رسمی افتتاحیه جلسه را اعلام می کنیم - در کلاس اصلی برنامه:

کلاس عمومی PingPong (public static void main (String args) InterruptException را پرتاب می کند (بازی PingPongGame = جدید PingPongGame () ؛ game.startGame ()؛))

همانطور که می بینید ، هیچ چیز خشمگین در اینجا وجود ندارد. این در حال حاضر فقط مقدمه ای برای چند رشته ای است ، اما شما قبلاً می دانید که چگونه کار می کند ، و می توانید آزمایش کنید - مدت زمان بازی را نه با تعداد ضربه ، بلکه برای مثال با زمان محدود کنید. بعداً به مبحث چند رشته ای برمی گردیم - به بسته java.util.conurrent ، کتابخانه Akka و مکانیسم فرار نگاه می کنیم. بیایید در مورد پیاده سازی چند رشته ای در پایتون نیز صحبت کنیم.

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

بنابراین این قوانین چیست؟

برنامه نویس دیگری که با مشکلی روبرو شده است ، فکر می کند: "اوه ، دقیقاً ، ما باید عبارات معمولی را اعمال کنیم." و اکنون او در حال حاضر دو مشکل دارد - جیمی زاوینسکی.

برنامه نویس دیگری که با مشکلی روبرو شده است ، فکر می کند: "اوه ، درست است ، من از جریانها در اینجا استفاده می کنم." و اکنون او ده مشکل دارد - بیل شیندلر.

برنامه نویسان زیادی که متعهد به نوشتن کد چند رشته ای می شوند ، مانند قهرمان تصنیف گوته در دام می افتند. شاگرد جادوگر". برنامه نویس یاد می گیرد که چگونه یک سری نخ ایجاد کند که در اصل کار می کند ، اما دیر یا زود آنها از کنترل خارج می شوند و برنامه نویس نمی داند چه باید بکند.

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

شاید با خود فکر کرده باشید: چرا این اتفاق می افتد؟ یک تصور غلط رایج این است: "برنامه نویسی چند رشته ای بسیار مشکل است." اما این مورد نیست. اگر یک برنامه چند رشته ای غیرقابل اعتماد باشد ، معمولاً به دلایل مشابه یک برنامه تک رشته ای با کیفیت پایین شکست می خورد. فقط این است که برنامه نویس روشهای اساسی ، شناخته شده و اثبات شده توسعه را دنبال نمی کند. به نظر می رسد برنامه های چند رشته ای پیچیده تر هستند ، زیرا هرچه نخ های موازی بیشتر اشتباه کنند ، آشفتگی بیشتری ایجاد می کنند - و بسیار سریعتر از یک نخ واحد.

تصور غلط در مورد "پیچیدگی برنامه نویسی چند رشته ای" به دلیل توسعه دهندگانی که به طور حرفه ای در نوشتن کد تک رشته ای توسعه یافته اند ، ابتدا با چند رشته ای روبرو شده اند و با آن کنار نیامده اند ، گسترده شده است. اما آنها به جای تجدید نظر در سوگیری ها و عادات کاری خود ، سرسختانه این واقعیت را ثابت می کنند که به هیچ وجه نمی خواهند کار کنند. این افراد با بهانه آوردن نرم افزارهای غیرقابل اعتماد و از دست رفتن مهلت ها ، همین مطلب را تکرار می کنند: "برنامه نویسی چند رشته ای بسیار مشکل است."

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

هیچ چیز در مورد بهبود میانگین برنامه تک رشته ای و تبدیل آن به یک برنامه چند رشته ای دشوار نیست. حداقل نباید باشد. مشکلات به دو دلیل ایجاد می شود:

  • برنامه نویسان نمی دانند چگونه از روشهای توسعه ساده و شناخته شده استفاده کنند.
  • اکثر اطلاعات ارائه شده در کتابهای مربوط به برنامه نویسی چند رشته ای از نظر فنی صحیح است ، اما برای حل مشکلات کاربردی کاملاً کاربردی نیست.

مهمترین مفاهیم برنامه نویسی جهانی هستند. آنها به همان اندازه برای برنامه های تک رشته ای و چند رشته ای قابل اجرا هستند. برنامه نویسان غرق در طوفان جریانها به سادگی با تسلط بر کد تک رشته ای درس های مهمی نیاموختند. من می توانم این را بگویم زیرا چنین توسعه دهندگانی در برنامه های چند رشته ای و تک رشته ای اشتباهات اساسی مشابهی را مرتکب می شوند.

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

در یک برنامه متوالی تک رشته ای ، احتمال خرابی داده ها مستقیماً با تعداد اجزایی که می توانند داده ها را تغییر دهند متناسب است.

به عنوان یک قاعده ، رهایی کامل از وضعیت جهانی امکان پذیر نیست ، اما توسعه دهنده ابزارهای بسیار موثری در زرادخانه خود دارد که به شما امکان می دهد به طور دقیق کنترل کنید که کدام اجزای برنامه می توانند وضعیت را تغییر دهند. علاوه بر این ، ما نحوه ایجاد لایه های محدود کننده API را در اطراف ساختارهای داده اولیه آموختیم. بنابراین ، ما کنترل خوبی بر نحوه تغییر این ساختار داده ها داریم.

مشکلات حالت متغیر جهانی به تدریج در اواخر دهه 80 و اوایل دهه 90 با گسترش برنامه نویسی مبتنی بر رویداد آشکار شد. برنامه ها دیگر "از ابتدا" شروع نمی شوند و یا یک مسیر قابل پیش بینی اجرا را "تا انتها" دنبال می کنند. برنامه های مدرن ، پس از خروج از آن ، وقایع اولیه ای دارند - به ترتیب غیرقابل پیش بینی ، با فواصل زمانی متغیر. کد تک رشته ای باقی می ماند ، اما در حال حاضر ناهمزمان می شود. احتمال فساد داده ها دقیقاً افزایش می یابد زیرا ترتیب وقوع رویدادها بسیار مهم است. شرایطی از این دست کاملاً متداول است: اگر رویداد B بعد از رویداد A رخ دهد ، همه چیز خوب کار می کند. اما اگر رویداد A بعد از رویداد B رخ دهد و رویداد C زمان دخالت بین آنها را داشته باشد ، ممکن است داده ها تا حد قابل تشخیص تحریف شوند.

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

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

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

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

ممکن است کسی استدلال کند که گاهی اوقات زمانی برای چنین طراحی ظریف برنامه وجود ندارد ، که بدون دولت جهانی امکان پذیر است. من معتقدم که وقت گذاشتن در این زمینه ممکن و ضروری است. هیچ چیز به اندازه تلاش برای مقابله با وضعیت تغییرپذیر جهانی بر برنامه های چند رشته ای تأثیر مخرب نمی گذارد. هرچه جزئیات بیشتری برای مدیریت داشته باشید ، احتمال اینکه برنامه شما به اوج برسد و خراب شود بیشتر است.

در برنامه های واقع بینانه ، باید وضعیت مشترک خاصی وجود داشته باشد که می تواند تغییر کند. و این جایی است که اکثر برنامه نویسان با مشکلات روبرو می شوند. برنامه نویس می بیند که در اینجا یک حالت مشترک لازم است ، به زرادخانه چند رشته ای روی می آورد و ساده ترین ابزار را از آنجا می گیرد: یک قفل جهانی (بخش بحرانی ، mutex یا هرطور که آن را نام می برند). به نظر می رسد آنها معتقدند که محرومیت متقابل همه مشکلات به اشتراک گذاری داده ها را حل می کند.

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

این بیانیه را می توان به دو صورت بیان کرد:

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

هر دوی این تفاسیر صحیح است.

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

تلاش برای استفاده از قفل ها برای حل مشکلات به اشتراک گذاری داده ها مانند خاموش کردن آتش با اکسیژن مایع است. مانند آتش سوزی ، پیشگیری از چنین مشکلاتی آسان تر از رفع آن است. اگر از حالت مشترک خلاص شوید ، لازم نیست از ابتدایی های همگام سازی نیز سوء استفاده کنید.

بیشتر آنچه شما در مورد چند رشته ای می دانید بی ربط است

در آموزش های چند رشته ای برای مبتدیان ، خواهید آموخت که موضوعات چیست. سپس نویسنده شروع به بررسی راههای مختلفی می کند که چگونه این موضوعات می توانند به طور موازی کار کنند - به عنوان مثال ، در مورد کنترل دسترسی به داده های مشترک با استفاده از قفل ها و نشانه ها صحبت می کند و در مورد کارهایی که هنگام کار با رویدادها ممکن است رخ دهد صحبت می کند. متغیرهای شرایط ، موانع حافظه ، بخشهای مهم ، mutexes ، میدانهای فرار و عملیات اتمی را از نزدیک مورد بررسی قرار خواهد داد. نمونه هایی از نحوه استفاده از این ساختارهای سطح پایین برای انجام انواع عملیات سیستم مورد بحث قرار خواهد گرفت. پس از خواندن این مطالب به نصف ، برنامه نویس تصمیم می گیرد که از قبل در مورد همه این بدوی ها و استفاده از آنها اطلاعات کافی دارد. پس از همه ، اگر بدانم که این چیز در سطح سیستم چگونه کار می کند ، می توانم آن را به همان شیوه در سطح برنامه اعمال کنم. آره؟

تصور کنید به یک نوجوان بگویید که چگونه موتور احتراق داخلی را خودش جمع کند. سپس ، بدون هیچ گونه آموزش رانندگی ، او را پشت فرمان خودرو می گذارید و می گویید: "رانندگی کن!" نوجوان نحوه کار یک ماشین را درک می کند ، اما نمی داند چگونه از نقطه A به نقطه B برسد.

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

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

این برنامه ها اغلب کاملاً در مدل ارائه دهنده و مصرف کننده مناسب هستند ، که فقط به سه رشته نیاز دارد:

  • جریان ورودی داده ها را می خواند و آنها را در صف ورودی قرار می دهد.
  • یک موضوع کارگر پرونده ها را از صف ورودی می خواند ، آنها را پردازش می کند و نتایج را در صف خروجی قرار می دهد.
  • جریان خروجی نوشته های صف خروجی را می خواند و آنها را ذخیره می کند.

این سه رشته به طور مستقل کار می کنند ، ارتباط بین آنها در سطح صف اتفاق می افتد.

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

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

مهمتر از همه ، زبانهای برنامه نویسی مدرن و کتابخانه ها ایجاد برنامه های تولید کننده و مصرف کننده را بسیار آسان می کنند. در دات نت ، مجموعه های موازی و کتابخانه TPL Dataflow را خواهید یافت. جاوا دارای سرویس مجری و BlockingQueue و کلاس های دیگر از فضای نام java.util.concurrent است. C ++ دارای یک کتابخانه تقویت موضوعی و کتابخانه Thread Building Blocks اینتل است. ویژوال استودیو 2013 مایکروسافت عوامل ناهمزمان را معرفی می کند. کتابخانه های مشابه نیز در زبان های پایتون ، جاوا اسکریپت ، روبی ، PHP و تا آنجا که من می دانم ، بسیاری از زبان های دیگر موجود است. شما می توانید با استفاده از هر یک از این بسته ها ، یک برنامه تولید کننده-مصرف کننده ایجاد کنید ، بدون اینکه مجبور باشید به قفل ها ، نشانه ها ، متغیرهای شرایط یا سایر اصول اولیه همگام سازی متوسل شوید.

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

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

کار با کتابخانه ها

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

توسعه دهندگان مدرن باید بسیاری از مشکلات را در سطح برنامه نویسی برنامه حل کنند ، این اتفاق می افتد که به سادگی زمانی برای فکر کردن درباره آنچه در سطح سیستم اتفاق می افتد وجود ندارد. هرچه برنامه های کاربردی پیچیده تر شوند ، جزئیات پیچیده تری باید بین سطوح API پنهان شوند. ما بیش از ده سال است که این کار را انجام می دهیم. می توان استدلال کرد که پنهان نمودن کیفی پیچیدگی سیستم از برنامه نویس دلیل اصلی این است که برنامه نویس قادر به نوشتن برنامه های کاربردی مدرن است. از این جهت ، آیا ما با پیاده سازی حلقه پیام UI ، ساخت پروتکل های ارتباطی سطح پایین و غیره پیچیدگی سیستم را پنهان نمی کنیم؟

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

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

متأسفانه ، مطالبی به زبان روسی در مورد موضوع چند رشته ای در پایتون وجود ندارد ، و پیتونرهایی که چیزی نشنیده بودند ، به عنوان مثال ، در مورد GIL ، با نظم قابل حسودی به من برخورد کردند. در این مقاله سعی می کنم اساسی ترین ویژگی های پایتون چند رشته ای را شرح دهم ، به شما بگویم GIL چیست و چگونه با آن (یا بدون آن) زندگی کنید و موارد دیگر.


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

برنامه های چند رشته ای


پایتون یک ماژول داردنخ زدن ، و هر آنچه برای برنامه نویسی چند رشته ای نیاز دارید را در خود دارد: انواع مختلفی از قفل ها ، و یک semaphore و مکانیزم رویداد وجود دارد. در یک کلمه - همه آنچه برای اکثریت قریب به اتفاق برنامه های چند رشته ای لازم است. علاوه بر این ، استفاده از همه این ابزارها بسیار ساده است. بیایید نمونه ای از برنامه ای را که 2 موضوع را شروع می کند ، در نظر بگیریم. یک موضوع ده "0" ، دیگری "ده" 1 وبه شدت به نوبه خود

وارد کردن نخ

def نویسنده

برای i in xrange (10):

چاپ x

Event_for_set.set ()

# رویداد اولیه

e1 = threading.vent ()

e2 = threading.vent ()

# نخ اولیه

0 ، e1 ، e2))

1 ، e2 ، e1))

# شروع تاپیک

t1.start ()

t2.start ()

t1. پیوستن به ()

t2. پیوستن ()


بدون کد جادویی یا وودو کد واضح و سازگار است. علاوه بر این ، همانطور که می بینید ، ما یک جریان از یک تابع ایجاد کرده ایم. این برای کارهای کوچک بسیار مناسب است. این کد نیز کاملاً انعطاف پذیر است. فرض کنید ما یک فرایند سوم داریم که "2" را می نویسد ، سپس کد به این شکل خواهد بود:

وارد کردن نخ

def نویسنده (x ، event_for_wait ، event_for_set):

برای i in xrange (10):

Event_for_wait.wait () # منتظر رویداد باشید

Event_for_wait.clear () # رویداد تمیز برای آینده

چاپ x

Event_for_set.set () # رویداد را برای موضوع همسایه تنظیم کنید

# رویداد اولیه

e1 = threading.vent ()

e2 = threading.vent ()

e3 = threading.vent ()

# نخ اولیه

t1 = threading. موضوع (target = author، args = ( 0 ، e1 ، e2))

t2 = threading. موضوع (target = author، args = ( 1 ، e2 ، e3))

t3 = threading. موضوع (target = author، args = ( 2 ، e3 ، e1))

# شروع تاپیک

t1.start ()

t2.start ()

t3. شروع ()

e1.set () # اولین رویداد را آغاز کنید

# موضوع را به موضوع اصلی وصل کنید

t1. پیوستن به ()

t2. پیوستن ()

t3. پیوستن ()


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

قفل مترجم جهانی


دو دلیل متداول برای استفاده از نخ ها وجود دارد: اول ، افزایش کارایی استفاده از معماری چند هسته ای پردازنده های مدرن و در نتیجه عملکرد برنامه.
ثانیاً ، اگر نیاز داریم منطق برنامه را به بخشهای موازی ، کاملاً یا تا حدی ناهمزمان تقسیم کنیم (به عنوان مثال ، بتوانیم چندین سرور را به طور همزمان پینگ کنیم).

در مورد اول ، ما با چنین محدودیتی در Python (یا بهتر بگوییم پیاده سازی اصلی CPython آن) ​​، مانند Global Interpreter Lock (یا به اختصار GIL) روبرو هستیم. مفهوم GIL این است که فقط یک رشته می تواند توسط پردازنده در یک زمان اجرا شود. این کار به گونه ای انجام می شود که بین رشته ها برای متغیرهای جداگانه دعوا وجود نداشته باشد. نخ اجرایی به کل محیط دسترسی پیدا می کند. این ویژگی پیاده سازی موضوع در پایتون کار با نخ ها را بسیار ساده کرده و امنیت موضوع خاصی را ارائه می دهد.

اما یک نکته ظریف وجود دارد: ممکن است به نظر برسد که یک برنامه چند رشته ای دقیقاً همان مدت زمان یک برنامه تک رشته ای را انجام می دهد یا مجموع زمان اجرای هر رشته روی CPU. اما در اینجا یک اثر ناخوشایند در انتظار ما است. برنامه را در نظر بگیرید:

با باز کردن ("test1.txt" ، "w") به عنوان fout:

برای i in xrange (1000000):

چاپ >> fout، 1


این برنامه فقط یک میلیون خط "1" را روی یک فایل می نویسد و این کار را در 0.35 ثانیه در رایانه من انجام می دهد.

برنامه دیگری را در نظر بگیرید:

از threading import موضوع

def writer (نام فایل ، n):

با باز کردن (نام فایل ، "w") به عنوان fout:

برای i در xrange (n):

چاپ >> fout، 1

t1 = موضوع (target = author، args = ("test2.txt" ، 500000 ،))

t2 = موضوع (target = author، args = ("test3.txt" ، 500000 ،))

t1.start ()

t2.start ()

t1. پیوستن به ()

t2. پیوستن ()


این برنامه 2 رشته ایجاد می کند. در هر موضوع ، نیم میلیون خط "1" را در یک فایل جداگانه می نویسد. در واقع میزان کار همانند برنامه قبلی است. اما با گذشت زمان ، یک اثر جالب در اینجا به دست می آید. این برنامه می تواند از 0.7 ثانیه تا حداکثر 7 ثانیه اجرا شود. چرا این اتفاق می افتد؟

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

UPD: در حال حاضر ، در Python 3.2 اجرای GIL بهبود یافته است ، که در آن این مشکل تا حدی حل شده است ، به ویژه به این دلیل که هر موضوع ، پس از از دست دادن کنترل ، مدت کوتاهی قبل از آن منتظر می ماند می تواند دوباره GIL را ضبط کند (ارائه خوبی به زبان انگلیسی وجود دارد)

شما می پرسید: "بنابراین نمی توانید برنامه های چند رشته ای کارآمد را در پایتون بنویسید؟" نه ، البته ، راهی وجود دارد ، و حتی چندین مورد.

برنامه های کاربردی چند پردازشی


به منظور حل مشکل توصیف شده در پاراگراف قبلی ، پایتون دارای یک ماژول استفرایند فرعی ... ما می توانیم برنامه ای بنویسیم که می خواهیم در یک رشته موازی اجرا کنیم (در واقع ، در حال حاضر یک فرایند است). و آن را در یک یا چند موضوع در برنامه دیگر اجرا کنید. این امر واقعاً برنامه ما را سرعت می بخشد ، زیرا نخ هایی که در پرتاب کننده GIL ایجاد می شوند جمع نمی شوند ، بلکه فقط منتظر می مانند تا روند اجرا به پایان برسد. با این حال ، این روش مشکلات زیادی دارد. مشکل اصلی این است که انتقال داده بین فرآیندها دشوار می شود. شما باید به نحوی اشیاء را سریال کنید ، از طریق PIPE یا سایر ابزارها ارتباط برقرار کنید ، اما همه اینها ناگزیر سربار است و درک کد دشوار می شود.

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

علاوه بر این ، ماژول چند پردازشی مکانیزمی برای کار با حافظه مشترک دارد. برای این کار ، ماژول دارای کلاسهای متغیر (Value) و آرایه (Array) است که می تواند بین فرآیندها "به اشتراک گذاشته شود". برای راحتی کار با متغیرهای مشترک ، می توانید از کلاس های مدیر استفاده کنید. انعطاف پذیرتر و استفاده از آنها آسان تر ، اما کندتر است. لازم به ذکر است که فرصت خوبی برای ایجاد انواع متداول از ماژول ctypes با استفاده از ماژول multiprocessing.sharedctypes وجود دارد.

همچنین در ماژول چند پردازشی مکانیزمی برای ایجاد مجموعه های فرآیند وجود دارد. استفاده از این مکانیزم برای پیاده سازی الگوی Master-Worker یا اجرای نقشه موازی (که به یک معنا مورد خاصی از Master-Worker است) بسیار مناسب است.

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

if __name__ == "__main__":


با این حال ، این طرح در حال حاضر فرم خوبی است.

چه چیز دیگری...


کتابخانه ها و رویکردهای دیگری برای نوشتن برنامه های موازی در پایتون وجود دارد. به عنوان مثال ، می توانید از Hadoop + Python یا پیاده سازی های مختلف Python MPI (pyMPI ، mpi4py) استفاده کنید. حتی می توانید از بسته بندی کتابخانه های C ++ یا Fortran موجود استفاده کنید. در اینجا می توان به چارچوب ها / کتابخانه هایی مانند Pyro ، Twisted ، Tornado و بسیاری دیگر اشاره کرد. اما همه اینها در حال حاضر خارج از حوصله این مقاله است.

اگر سبک من را دوست داشتید ، در مقاله بعدی سعی می کنم به شما بگویم که چگونه مترجمان ساده را در PLY بنویسید و برای چه مواردی می توان از آنها استفاده کرد

فصل 10

برنامه های چند رشته ای

انجام چند وظیفه در سیستم عامل های مدرن امری بدیهی تلقی می شود [ قبل از ظهور Apple OS X ، هیچ سیستم عامل مدرن چند وظیفه ای در رایانه های Macintosh وجود نداشت. طراحی یک سیستم عامل با چند وظیفه کامل بسیار دشوار است ، بنابراین OS X باید بر اساس سیستم یونیکس باشد.]. کاربر انتظار دارد وقتی ویرایشگر متن و سرویس گیرنده نامه به طور همزمان راه اندازی می شوند ، این برنامه ها با هم تضاد نداشته باشند و هنگام دریافت ایمیل ، ویرایشگر کار خود را متوقف نمی کند. هنگامی که چندین برنامه به طور همزمان راه اندازی می شوند ، سیستم عامل به سرعت بین برنامه ها جابجا می شود و به نوبه خود پردازنده ای را در اختیار آنها قرار می دهد (البته اگر چند پردازنده روی رایانه نصب نشده باشد). در نتیجه، توهماجرای چندین برنامه به طور همزمان ، زیرا حتی بهترین تایپیست (و سریعترین اتصال به اینترنت) نمی تواند از پردازنده مدرن پشتیبانی کند.

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

VB هرگز از پشتیبانی چند رشته ای معمولی برخوردار نبوده است. درست است ، یکی از انواع آن در VB5 ظاهر شد - مدل جریان مشترک(نخ آپارتمان). همانطور که به زودی خواهید دید ، مدل مشارکتی برخی از مزایای چند رشته ای را در اختیار برنامه نویس قرار می دهد ، اما از تمام ویژگی ها به طور کامل استفاده نمی کند. دیر یا زود ، شما باید از یک دستگاه آموزشی به یک ماشین واقعی تبدیل شوید و VB .NET با پشتیبانی از یک مدل چند رشته ای رایگان ، اولین نسخه VB شد.

با این حال ، چند رشته ای یکی از ویژگی هایی نیست که به راحتی در زبان های برنامه نویسی پیاده سازی شده و به راحتی توسط برنامه نویسان تسلط پیدا می کند. چرا؟

از آنجا که در برنامه های چند رشته ای ، خطاهای بسیار پیچیده ای ممکن است رخ دهد که به طور غیرقابل پیش بینی ظاهر می شوند و ناپدید می شوند (و اشکال زدایی چنین خطاهایی سخت ترین آنها است).

صادقانه هشدار: چند رشته ای یکی از سخت ترین زمینه های برنامه نویسی است. کوچکترین بی توجهی منجر به بروز خطاهای گریزان می شود که تصحیح آنها مبالغ نجومی می گیرد. به همین دلیل ، این فصل شامل موارد زیادی است بدمثالها - ما آنها را عمداً به گونه ای نوشتیم تا خطاهای رایج را نشان دهیم. این امن ترین رویکرد برای یادگیری برنامه نویسی چند رشته ای است: شما باید بتوانید مشکلات احتمالی را زمانی تشخیص دهید که در نگاه اول همه چیز خوب کار می کند و نحوه حل آنها را بدانید. اگر می خواهید از تکنیک های برنامه نویسی چند رشته ای استفاده کنید ، بدون آن نمی توانید این کار را انجام دهید.

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

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

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

معرفی چند رشته ای

هر برنامه در یک برنامه خاص کار می کند متن نوشته،توصیف توزیع کد و داده ها در حافظه با ذخیره زمینه ، وضعیت جریان برنامه در واقع ذخیره می شود ، که به شما امکان می دهد آن را در آینده بازیابی کرده و اجرای برنامه را ادامه دهید.

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

  • عملکرد برنامه به طور واضح و طبیعی به چندین عملیات ناهمگن تقسیم می شود ، مانند مثال دریافت ایمیل و تهیه پیام های جدید.
  • این برنامه محاسبات طولانی و پیچیده ای را انجام می دهد و شما نمی خواهید رابط گرافیکی در طول محاسبات مسدود شود.
  • این برنامه بر روی یک رایانه چند پردازنده با یک سیستم عامل که از استفاده از چندین پردازنده پشتیبانی می کند اجرا می شود (تا زمانی که تعداد رشته های فعال از تعداد پردازنده ها فراتر نرود ، اجرای موازی عملاً عاری از هزینه های مربوط به تغییر موضوعات است).

قبل از حرکت به مکانیک برنامه های چند رشته ای ، لازم است به یک مورد اشاره کنم که اغلب باعث ایجاد سردرگمی در بین مبتدیان در زمینه برنامه نویسی چند رشته ای می شود.

یک رویه ، نه یک شیء ، در جریان برنامه اجرا می شود.

دشوار است بگوییم منظور از عبارت "شی در حال اجرا" چیست ، اما یکی از نویسندگان اغلب سمینارهای برنامه نویسی چند رشته ای را تدریس می کند و این سوال بیشتر از دیگران مطرح می شود. شاید کسی فکر کند که کار نخ برنامه با فراخوانی به متد New کلاس شروع می شود ، پس از آن نخ همه پیامهای ارسال شده به شی مربوط را پردازش می کند. این گونه بازنمایی ها کاملااشتباه هستند. یک شیء می تواند شامل چندین رشته باشد که روشهای مختلف (و گاهی حتی یکسان) را اجرا می کند ، در حالی که پیامهای شی با چندین نخ مختلف منتقل و دریافت می شوند (به هر حال ، این یکی از دلایلی است که برنامه نویسی چند رشته ای را پیچیده می کند: برای اشکال زدایی یک برنامه ، باید دریابید که کدام موضوع در یک لحظه معین این یا آن روش را انجام می دهد!).

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

روشهای متداول کلاسها را می توان در نخ های برنامه نیز اجرا کرد. در این مورد ، یک مورد مهم دیگر را نیز در نظر داشته باشید: نخ با خروج از روشی که برای آن ایجاد شده است به پایان می رسد. خاتمه عادی جریان برنامه تا زمان اتمام روش امکان پذیر نیست.

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

ویژگی های اصلی .NET مربوط به استفاده از نخ های برنامه ای در فضای نام Threading متمرکز شده است. بنابراین ، اکثر برنامه های چند رشته ای باید با خط زیر شروع شوند:

سیستم واردات. موضوع

وارد کردن یک فضای نامی ، تایپ برنامه شما را آسان تر می کند و فناوری IntelliSense را فعال می کند.

ارتباط مستقیم جریانها با رویه ها نشان می دهد که در این تصویر ، نمایندگان(به فصل 6 مراجعه کنید). به طور خاص ، فضای نام Threading شامل نماینده ThreadStart است که معمولاً هنگام راه اندازی موضوعات برنامه استفاده می شود. نحو استفاده از این نماینده به این شکل است:

نماینده عمومی زیر ThreadStart ()

کدی که با نماینده ThreadStart فراخوانی می شود نباید پارامتر یا مقدار بازگشتی داشته باشد ، بنابراین نخ ها برای توابع (که مقدار را برمی گردانند) و برای رویه هایی با پارامترها ایجاد نمی شوند. برای انتقال اطلاعات از جریان ، شما همچنین باید به دنبال ابزارهای جایگزین باشید ، زیرا روشهای اجرا شده مقادیر را بر نمی گردانند و نمی توانند از انتقال با مرجع استفاده کنند. به عنوان مثال ، اگر ThreadMethod در کلاس WilluseThread قرار دارد ، ThreadMethod می تواند اطلاعات را با تغییر در ویژگی های نمونه های کلاس WillUseThread به اشتراک بگذارد.

دامنه های برنامه

موضوعات .NET در اصطلاحاً دامنه های برنامه اجرا می شوند ، که در اسناد به عنوان "سندباسی که برنامه در آن اجرا می شود" تعریف شده است. دامنه برنامه را می توان نسخه ای سبک از فرایندهای Win32 دانست. یک فرایند Win32 می تواند شامل چندین دامنه برنامه باشد. تفاوت اصلی بین حوزه های کاربردی و فرایندها این است که یک فرآیند Win32 دارای فضای آدرس خاص خود است (در اسناد ، حوزه های برنامه نیز با فرآیندهای منطقی که در داخل یک فرایند فیزیکی اجرا می شوند مقایسه می شود). در NET ، تمام مدیریت حافظه توسط زمان اجرا انجام می شود ، بنابراین چندین دامنه برنامه می توانند در یک فرایند Win32 اجرا شوند. یکی از مزایای این طرح بهبود مقیاس پذیری برنامه ها است. ابزارهای کار با دامنه های برنامه در کلاس AppDomain قرار دارد. توصیه می کنیم مستندات این کلاس را مطالعه کنید. با کمک آن می توانید در مورد محیطی که برنامه شما در آن اجرا می شود اطلاعات کسب کنید. به طور خاص ، کلاس AppDomain هنگام انجام بازتاب در کلاس های سیستم .NET استفاده می شود. برنامه زیر مجموعه های بارگذاری شده را لیست می کند.

سیستم واردات. بازتاب

ماژول ماژول

فرعی اصلی ()

Dim theDomain را به عنوان AppDomain کم کنید

theDomain = AppDomain.CurrentDomain

Dim Assemblies () As

Assemblies = theDomain.GetAssemblies

Dim anAssemblyxAs

برای هر anAssembly در مجامع

Console.WriteLinetanAssembly.Full Name) بعد

Console.ReadLine ()

End Sub

ماژول پایان

ایجاد جریان ها

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

کلاس عمومی WillUseThreads

Public SubtractFromCounter ()

Dim شمارش صحیح

Do while True count - = 1 انجام دهید

Console.WriteLlne ("در نخ و شمارنده دیگر هستم"

& شمردن)

حلقه

End Sub

پایان کلاس

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

قطعه زیر روش Sub Main را که موضوع را آغاز می کند و دستور Imports را نشان می دهد:

گزینه سختگیرانه در سیستم واردات. ماژول ماژول موضوع

فرعی اصلی ()

1 Dim myTest As New WillUseThreads ()

2 Dim bThreadStart As New ThreadStart (آدرسOf _

myTest.SubtractFromCounter)

3 Dim bThread به عنوان موضوع جدید (bThreadStart)

4 اینچ bThread.Start ()

Dim i As Integer

5 در حالی که درست است عمل کنید

Console.WriteLine ("در موضوع اصلی و تعداد" & i) i + = 1

حلقه

End Sub

ماژول پایان

بیایید به ترتیب مهمترین نکات را بررسی کنیم. اول از همه ، روش Sub Man n همیشه کار می کند مسیر اصلی(موضوع اصلی) در برنامه های دات نت ، همیشه حداقل دو موضوع وجود دارد: نخ اصلی و نخ جمع آوری زباله. خط 1 نمونه جدیدی از کلاس تست ایجاد می کند. در خط 2 ، ما یک نماینده ThreadStart ایجاد می کنیم و آدرس روش SubtractFromCounter را به نمونه کلاس آزمایشی ایجاد شده در خط 1 منتقل می کنیم (این روش بدون پارامتر نامیده می شود). خوببا وارد کردن فضای نام Threading می توان نام طولانی را حذف کرد. شیء موضوع جدید در خط 3 ایجاد می شود. هنگام فراخوانی سازنده کلاس موضوع به عبور نماینده ThreadStart توجه کنید. برخی از برنامه نویسان ترجیح می دهند این دو خط را به یک خط منطقی متصل کنند:

Dim bThread As New Thread (New ThreadStarttAddressOf _

myTest.SubtractFromCounter))

در نهایت ، خط 4 با فراخوانی روش شروع نمونه موردی که برای نماینده ThreadStart ایجاد شده است ، موضوع را "شروع" می کند. با فراخوانی این روش ، ما به سیستم عامل می گوییم که روش Subtract باید در یک موضوع جداگانه اجرا شود.

کلمه "شروع می شود" در پاراگراف قبلی داخل علامت نقل قول قرار گرفته است ، زیرا این یکی از عجیب و غریب برنامه نویسی چند رشته ای است: فراخوانی Start در واقع موضوع را شروع نمی کند! فقط به سیستم عامل می گوید که موضوع مشخص شده را برای اجرا برنامه ریزی کند ، اما مستقیماً شروع به کار نمی کند. شما قادر نخواهید بود به تنهایی اجرای رشته ها را شروع کنید ، زیرا سیستم عامل همیشه اجرای رشته ها را کنترل می کند. در بخش بعدی ، نحوه استفاده از اولویت را برای شروع سریعتر سیستم عامل در موضوع خود خواهید آموخت.

در شکل 10.1 نمونه ای از آنچه می تواند پس از شروع برنامه و سپس قطع آن با کلید Ctrl + Break رخ دهد را نشان می دهد. در مورد ما ، یک موضوع جدید تنها پس از افزایش شمارنده در موضوع اصلی به 341 شروع شد!

برنج. 10.1 زمان اجرای نرم افزار چند رشته ای ساده

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

برنج. 10.2 جابجایی بین موضوعات در یک برنامه چند رشته ای ساده

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

از آنجا که برنامه های کوانتیزاسیون همه نسخه های ویندوز که دات نت اجرا می شوند ، در برنامه نویسی دات نت ، برای هر رشته حداقل یک بازه زمانی اختصاص داده شده است ، مشکلات استفاده از CPU انحصاری چندان جدی نیستند. از سوی دیگر ، اگر چارچوب .NET برای سیستم های دیگر تطبیق داده شود ، ممکن است تغییر کند.

اگر قبل از فراخوانی Start خط زیر را در برنامه خود بگنجانیم ، حتی نخ هایی با کمترین اولویت بخشی از زمان CPU را دریافت می کنند:

bThread.Priority = ThreadPriority.Highhest

برنج. 10.3 رشته ای که بیشترین اولویت را دارد معمولاً سریعتر راه اندازی می شود

برنج. 10.4 پردازنده همچنین برای موضوعات با اولویت پایین ارائه شده است

فرمان حداکثر اولویت را به موضوع جدید اختصاص می دهد و اولویت موضوع اصلی را کاهش می دهد. شکل. 10.3 مشاهده می شود که موضوع جدید سریعتر از قبل شروع به کار می کند ، اما ، همانطور که در شکل 1 نشان داده شده است. 10.4 ، موضوع اصلی نیز کنترل را دریافت می کندتنبلی (البته برای مدت بسیار کوتاهی و فقط پس از کار طولانی مدت جریان با تفریق). هنگامی که برنامه را روی رایانه های خود اجرا می کنید ، نتایج مشابهی را که در شکل نشان داده شده است دریافت خواهید کرد. 10.3 و 10.4 ، اما به دلیل تفاوت بین سیستم های ما ، هیچ تطبیقی ​​دقیق وجود نخواهد داشت.

نوع شمارش شده ThreadPrlority شامل مقادیر پنج سطح اولویت است:

ThreadPriotity. بالاترین

ThreadPriority.AboveNormal

ThreadPrlority عادی

ThreadPriority.BelowNormal

ThreadPriority.Lowest

روش پیوستن

گاهی اوقات یک موضوع برنامه باید تا پایان موضوع دیگر به حالت تعلیق درآید. فرض کنید می خواهید نخ 1 را موقتاً متوقف کنید تا زمانی که موضوع 2 محاسبه خود را کامل کند. برای این از جریان 1متد Join برای جریان 2 فراخوانی شده است. به عبارت دیگر ، فرمان

موضوع 2. پیوستن به ()

نخ فعلی را معلق کرده و منتظر می ماند تا نخ 2 کامل شود. موضوع 1 به حالت قفل شده

اگر با استفاده از روش Join به جریان 1 به جریان 2 ملحق شوید ، سیستم عامل به طور خودکار جریان 1 را پس از جریان 2 شروع می کند. توجه داشته باشید که مراحل راه اندازی غیرقطعی:نمی توان دقیق گفت چه مدت بعد از پایان نخ 2 ، نخ 1 شروع به کار می کند. نسخه دیگری از Join وجود دارد که مقدار بولین را برمی گرداند:

موضوع 2. پیوستن (صحیح)

این روش یا منتظر می ماند تا نخ 2 کامل شود ، یا بعد از گذشت زمان مشخص شده ، موضوع 1 را مسدود می کند و باعث می شود زمانبند سیستم عامل دوباره زمان CPU را به نخ اختصاص دهد. این متد True را باز می کند اگر نخ 2 قبل از اتمام فاصله زمانی مشخص پایان یابد و در غیر این صورت False.

قانون اساسی را به خاطر بسپارید: آیا نخ 2 کامل شده یا زمان آن تمام شده است ، هیچ کنترلی بر فعال شدن نخ 1 ندارید.

نام موضوع ، CurrentThread و ThreadState

ویژگی Thread.CurrentThread یک مرجع را به شی موردی که در حال اجرا است باز می گرداند.

اگرچه یک پنجره موضوعی فوق العاده برای اشکال زدایی برنامه های چند رشته ای در VB .NET وجود دارد ، که در زیر توضیح داده شده است ، اما ما اغلب از دستور استفاده می کنیم

MsgBox (Thread.CurrentThread.Name)

اغلب مشخص می شد که کد در یک موضوع کاملاً متفاوت که قرار بود از آن اجرا شود اجرا می شود.

به یاد بیاورید که اصطلاح "زمان بندی غیر قطعی جریان برنامه" به معنای یک چیز بسیار ساده است: برنامه نویس عملاً هیچ وسیله ای در اختیار ندارد که بر کار برنامه ریز تأثیر بگذارد. به همین دلیل ، برنامه ها اغلب از ویژگی ThreadState برای بازگرداندن اطلاعات در مورد وضعیت فعلی یک رشته استفاده می کنند.

پنجره جریان ها

پنجره Threads Visual Studio .NET در اشکال زدایی برنامه های چند رشته ای ارزشمند است. با دستور زیر منوی Debug> Windows در حالت وقفه فعال می شود. فرض کنید شما با دستور زیر نامی را به موضوع bThread اختصاص داده اید:

bThread.Name = "تفریق موضوع"

نمای تقریبی پنجره جریانها پس از قطع برنامه با ترکیب کلیدهای Ctrl + Break (یا به روش دیگر) در شکل نشان داده شده است. 10.5

برنج. 10.5 پنجره جریان ها

فلش در ستون اول ، نخ فعال را که توسط ویژگی Thread.CurrentThread بازگردانده می شود ، مشخص می کند. ستون شناسه شامل شناسه های رشته ای عددی است. ستون بعدی نام جریان (در صورت اختصاص) را فهرست می کند. ستون Location روش اجرا را نشان می دهد (برای مثال ، رویه WriteLine کلاس Console در شکل 10.5). ستونهای باقیمانده حاوی اطلاعاتی در مورد نخهای اولویت دار و معلق هستند (بخش بعدی را ببینید).

پنجره موضوع (نه سیستم عامل!) به شما امکان می دهد تا نخ های برنامه خود را با استفاده از منوهای زمینه کنترل کنید. به عنوان مثال ، می توانید با کلیک راست بر روی خط مربوطه و انتخاب دستور Freeze ، موضوع فعلی را متوقف کنید (بعداً ، نخ متوقف شده را می توان از سر گرفت). متوقف کردن موضوعات اغلب هنگام اشکال زدایی برای جلوگیری از تداخل یک موضوع خراب در برنامه استفاده می شود. علاوه بر این ، پنجره streams به شما امکان می دهد جریان دیگری (بدون توقف) را فعال کنید. برای انجام این کار ، روی خط مورد نیاز راست کلیک کرده و دستور Switch To Thread را از منوی زمینه انتخاب کنید (یا به سادگی روی خط موضوع دوبار کلیک کنید). همانطور که در زیر نشان داده می شود ، این در تشخیص بن بست های احتمالی بسیار مفید است.

تعلیق جریان

جریانهای بلا استفاده می توانند با استفاده از روش Slеer به حالت غیرفعال منتقل شوند. یک جریان منفعل نیز مسدود در نظر گرفته می شود. البته وقتی موضوعی در حالت غیرفعال قرار می گیرد ، بقیه نخ ها منابع پردازنده بیشتری خواهند داشت. نحو استاندارد روش Slеer به شرح زیر است: Thread.Sleep (interval_in_milliseconds)

در نتیجه تماس Sleep ، نخ فعال حداقل برای تعداد مشخصی از میلی ثانیه منفعل می شود (با این حال ، فعالسازی بلافاصله پس از انقضای فاصله مشخص شده تضمین نمی شود). لطفاً توجه داشته باشید: هنگام فراخوانی روش ، ارجاع به یک موضوع خاص منتقل نمی شود - روش Sleep فقط برای نخ فعال فراخوانی می شود.

نسخه دیگری از Sleep باعث می شود موضوع فعلی بقیه زمان اختصاص داده شده CPU را کنار بگذارد:

موضوع خواب (0)

گزینه بعدی موضوع فعلی را به مدت نامحدود در حالت غیرفعال قرار می دهد (فعالسازی فقط زمانی رخ می دهد که با Interrupt تماس بگیرید):

موضوع. باریک (زمان پایان نامحدود)

از آنجا که رشته های غیرفعال (حتی با یک بازه زمانی نامحدود) می توانند با روش Interrupt قطع شوند ، که به استثنا منجر به راه اندازی ThreadlnterruptExcepti می شود ، تماس Slayer همیشه در یک بلوک Try-Catch محصور می شود ، مانند قطعه زیر:

تلاش كردن

موضوع خواب (200)

"حالت غیرفعال نخ قطع شده است

Catch e As Exception

"سایر استثنائات

پایان امتحان

هر برنامه .NET بر روی یک رشته برنامه اجرا می شود ، بنابراین روش Sleep نیز برای تعلیق برنامه ها استفاده می شود (اگر فضای نام Threadipg توسط برنامه وارد نشده باشد ، باید از نام کاملاً واجد شرایط Threading.Thread. Sleep) استفاده کنید.

خاتمه یا قطع رشته های برنامه

هنگامی که متدی که هنگام ایجاد نمایندگی ThreadStart مشخص می شود ، یک موضوع به طور خودکار خاتمه می یابد ، اما گاهی اوقات در صورت بروز عوامل خاص ، متد (و از این رو نخ) باید خاتمه یابد. در چنین مواردی ، جریانها معمولاً بررسی می شوند متغیر شرطی ،بسته به حالت آندر مورد خروج اضطراری از جریان تصمیم گیری می شود. به طور معمول ، یک حلقه Do-while در این روش گنجانده شده است:

Sub ThreadedMethod ()

"برنامه باید ابزارهایی را برای نظرسنجی فراهم کند

"متغیر شرطی

"به عنوان مثال ، یک متغیر شرطی می تواند به عنوان یک ویژگی تنظیم شود

Do while conditionVariable = False And MoreWorkToDo

"کد اصلی

حلقه پایان ساب

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

اگر متغیر شرط باید در یک مکان خاص بررسی شود ، از دستور If-Then در ارتباط با Exit Sub در یک حلقه بی نهایت استفاده کنید.

دسترسی به یک متغیر شرطی باید همزمان شود تا قرار گرفتن در معرض سایر نخ ها مانع استفاده عادی آن نشود. این موضوع مهم در بخش "عیب یابی: همگام سازی" پوشش داده شده است.

متأسفانه ، کد موضوعات منفعل (یا مسدود شده) اجرا نمی شود ، بنابراین گزینه با نظرسنجی از یک متغیر شرطی برای آنها مناسب نیست. در این حالت ، متد Interrupt را روی متغیر شیء که حاوی ارجاع به نخ مورد نظر است فراخوانی کنید.

روش وقفه را فقط می توان در موضوعات Wait ، Sleep یا Join فراخوانی کرد. اگر برای موضوعی که در یکی از حالتهای ذکر شده قرار دارد ، Interrupt را فراخوانی کنید ، پس از مدتی موضوع دوباره شروع به کار می کند و محیط اجرا به استثنای یک موضوع ، یک ThreadlnterruptExcepti را در موضوع ایجاد می کند. این اتفاق می افتد حتی اگر نخ با تماس Thread.Sleepdimeout به طور نامحدود منفعل شده باشد. بي نهايت). ما می گوییم "بعد از مدتی" زیرا برنامه ریزی موضوع غیر قطعی است. ThreadlnterruptExcepti به استثنا توسط بخش Catch حاوی کد خروجی از حالت انتظار گرفته می شود. با این حال ، بخش Catch نیازی به قطع نخ در تماس قطع نمی کند - نخ به استثنای مورد نیاز خود رسیدگی می کند.

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

تعلیق و کشتن نخ ها

فضای نام Threading شامل روشهای دیگری است که رشته معمولی را قطع می کند:

  • تعلیق ؛
  • سقط کردن

گفتن اینکه چرا دات نت از این روش ها پشتیبانی می کند دشوار است - فراخوانی Suspend و Abort احتمالاً باعث ناپایدار شدن برنامه می شود. هیچ یک از روش ها امکان خالی سازی طبیعی جریان را ندارند. علاوه بر این ، هنگام فراخوانی Suspend یا Abort ، نمی توان پیش بینی کرد که نخ پس از تعلیق یا سقط در چه وضعیتی قرار می گیرد.

Calling Abort یک ThreadAbortException را پرتاب می کند. برای درک اینکه چرا این استثناء عجیب نباید در برنامه ها مورد استفاده قرار گیرد ، در اینجا قسمتی از مستندات .NET SDK آمده است:

"... هنگامی که یک نخ با فراخوانی Abort از بین می رود ، زمان اجرا ThreadAbortException را پرتاب می کند. این یک نوع استثناء خاص است که نمی تواند توسط برنامه گرفتار شود. هنگامی که این استثنا پرتاب می شود ، زمان اجرا قبل از خاتمه نخ ، تمام بلوک های Last را اجرا می کند. از آنجا که هر اقدامی می تواند در بلوک های آخر انجام شود ، با Join تماس بگیرید تا مطمئن شوید جریان از بین رفته است. "

اخلاقی: لغو و تعلیق توصیه نمی شود (و اگر هنوز نمی توانید بدون تعلیق کار کنید ، نخ معلق را با استفاده از روش رزومه از سر بگیرید). شما می توانید با ایمن کردن یک متغیر وضعیت همگام یا با فراخوانی روش وقفه در بالا ، با خیال راحت یک موضوع را خاتمه دهید.

موضوعات پس زمینه (دیمون ها)

هنگامی که سایر اجزای برنامه متوقف می شوند ، برخی از نخ هایی که در پس زمینه اجرا می شوند به طور خودکار متوقف می شوند. به طور خاص ، جمع آوری زباله در یکی از نخ های پس زمینه اجرا می شود. رشته های پس زمینه معمولاً برای دریافت داده ها ایجاد می شوند ، اما این کار تنها در صورتی انجام می شود که نخ های دیگر دارای کد هستند که می توانند داده های دریافتی را پردازش کنند. نحو: نام جریان. IsBackGround = درست است

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

مثال جدی تر: استخراج داده ها از کد HTML

ما توصیه می کنیم از جریانها فقط زمانی استفاده کنید که عملکرد برنامه به وضوح به چندین عملیات تقسیم شده باشد. یک مثال خوب برنامه استخراج HTML در فصل 9 است. کلاس ما دو کار را انجام می دهد: واکشی داده ها از آمازون و پردازش آنها. این یک مثال کامل از موقعیتی است که در آن برنامه نویسی چند رشته ای واقعاً مناسب است. ما برای چندین کتاب مختلف کلاس ایجاد می کنیم و سپس داده ها را در جریان های مختلف تجزیه می کنیم. ایجاد یک موضوع جدید برای هر کتاب ، کارایی برنامه را افزایش می دهد ، زیرا در حالی که یک موضوع اطلاعات دریافت می کند (که ممکن است نیاز به انتظار در سرور آمازون داشته باشد) ، یک موضوع دیگر مشغول پردازش داده هایی است که قبلاً دریافت شده است.

نسخه چند رشته ای این برنامه کارآمدتر از نسخه تک رشته ای است ، فقط در رایانه ای با چندین پردازنده یا اگر دریافت داده های اضافی را می توان به طور م withثر با تجزیه و تحلیل آنها ترکیب کرد.

همانطور که در بالا ذکر شد ، تنها رویه هایی که هیچ پارامتری ندارند می توانند در نخ ها اجرا شوند ، بنابراین باید تغییرات جزئی را در برنامه ایجاد کنید. در زیر روش اصلی است که برای حذف پارامترها بازنویسی شده است:

زیرنویس عمومی FindRank ()

m_Rank = ScrapeAmazon ()

Console.WriteLine ("رتبه" & m_Name & "Is" & GetRank)

End Sub

از آنجا که ما نمی توانیم از زمینه ترکیبی برای ذخیره و بازیابی اطلاعات استفاده کنیم (نوشتن برنامه های چند رشته ای با رابط گرافیکی در قسمت آخر این فصل مورد بحث قرار گرفته است) ، برنامه داده های چهار کتاب را در یک آرایه ذخیره می کند ، تعریف آن چنین آغاز می شود:

Dim TheBook (3.1) As String theBook (0.0) = "1893115992"

theBook (0.l) = "برنامه نویسی VB .NET" "و غیره

چهار جریان در یک چرخه ایجاد می شود که در آن اشیاء AmazonRanker ایجاد می شوند:

برای i = 0 تا 3

تلاش كردن

theRanker = AmazonRanker جدید (theBook (i.0). theBookd.1))

aThreadStart = ThreadStar جدید (آدرسOr theRanker.FindRan ()

aThread = موضوع جدید (aThreadStart)

aThread.Name = theBook (i.l)

aThread.Start () Catch e As Exception

Console.WriteLine (e.Message)

پایان امتحان

بعد

در زیر متن کامل برنامه آمده است:

گزینه Strict On Imports System.IO Imports System.Net

سیستم واردات. موضوع

ماژول ماژول

فرعی اصلی ()

Dim TheBook (3.1) As String را کم کنید

theBook (0.0) = "1893115992"

theBook (0.l) = "برنامه نویسی VB .NET"

theBook (l.0) = "1893115291"

theBook (l.l) = "برنامه نویسی پایگاه داده VB .NET"

theBook (2،0) = "1893115623"

theBook (2.1) = "برنامه نویس" با C #آشنا می شود. "

theBook (3.0) = "1893115593"

theBook (3.1) = "Gland the .Net Platform"

Dim i As Integer

Dim theRanker As = AmazonRanker

Dim aThreadStart As Threading.ThreadStart

Dim aThread As Threading.Thread

برای i = 0 تا 3

تلاش كردن

theRanker = کتاب جدید AmazonRankerttheBook (i.0). کتاب (i.1))

aThreadStart = ThreadStart جدید (آدرسOr theRanker. FindRank)

aThread = موضوع جدید (aThreadStart)

aThread.Name = theBook (i.l)

aThread.Start ()

Catch e As Exception

Console.WriteLlnete.Message)

پایان بعدی را امتحان کنید

Console.ReadLine ()

End Sub

ماژول پایان

کلاس عمومی AmazonRanker

m_URL خصوصی به عنوان رشته

m_Rank Private As Integer

m_Name خصوصی به عنوان رشته

Public Sub New (ByVal ISBN As String. ByVal theName As String)

m_URL = "http://www.amazon.com/exec/obidos/ASIN/" & ISBN

m_Name = theName End Sub

Public Sub FindRank () m_Rank = ScrapeAmazon ()

Console.Writeline ("رتبه" & m_Name & "is"

& GetRank) End Sub

عمومی Readonly Property GetRank () As String Get

اگر m_Rank<>0 سپس

CStr (m_Rank) دیگر را برگردانید

" چالش ها و مسائل

پایان اگر

پایان دریافت

ویژگی پایان

عمومی خواندنی GetName () As String Get

بازگشت m_Name

پایان دریافت

ویژگی پایان

عملکرد خصوصی ScrapeAmazon () As Integer Try

DimURURL را به عنوان Uri جدید (m_URL) کم کنید

Dim TheRequest را به عنوان WebRequest کاهش دهید

theRequest = WebRequest.Create (theURL)

Dim theResponse As WebResponse

theResponse = theRequest.GetResponse

Dim aReader As New StreamReader (theResponse.GetResponseStream ())

Dim theData As String را کم کنید

theData = aReader.ReadToEnd

تجزیه و تحلیل بازگشت (theData)

E را به عنوان استثنا بگیرید

Console.WriteLine (E.Message)

Console.WriteLine (E.StackTrace)

کنسول. ReadLine ()

End Try End Function

تجزیه و تحلیل عملکرد خصوصی (ByVal theData As String) بصورت صحیح

Dim Location As.Integer Location = theData.IndexOf (" Amazon.com

رتبه فروش:") _

+ "رتبه فروش Amazon.com:". طول

Dim temp As String

انجام دهید تا theData.Substring (Location.l) = "<" temp = temp

& theData.Substring (Location.l) مکان + = 1 حلقه

بازگشت Clnt (دما)

تابع پایان

پایان کلاس

عملیات چند رشته ای معمولاً در فضای نام. برای کسب اطلاعات بیشتر در مورد استفاده از روشهای ناهمزمان هنگام نوشتن برنامه های چند رشته ای ، به روشهای BeginGetResponse و EndGetResponse کلاس HTTPWebRequest مراجعه کنید.

خطر اصلی (داده های کلی)

تا کنون ، تنها مورد استفاده ایمن برای نخ ها در نظر گرفته شده است - جریانهای ما اطلاعات عمومی را تغییر نداد.اگر اجازه دهید داده های کلی تغییر کنند ، خطاهای احتمالی شروع به تکثیر تصاعدی می کنند و خلاص شدن از آنها برای برنامه بسیار مشکل تر می شود. از سوی دیگر ، اگر تغییر داده های به اشتراک گذاشته شده توسط نخ های مختلف را ممنوع کنید ، برنامه نویسی چند رشته ای NET به سختی با قابلیت های محدود VB6 متفاوت خواهد بود.

ما یک برنامه کوچک به شما ارائه می دهیم که مشکلات ایجاد شده را بدون وارد شدن به جزئیات غیر ضروری نشان می دهد. این برنامه یک خانه را با ترموستات در هر اتاق شبیه سازی می کند. اگر درجه حرارت 5 درجه فارنهایت یا بیشتر (حدود 2.77 درجه سانتیگراد) کمتر از دمای مورد نظر باشد ، ما به سیستم گرمایش دستور می دهیم دما را 5 درجه افزایش دهد. در غیر این صورت ، دما فقط 1 درجه افزایش می یابد. اگر دمای فعلی بیشتر یا مساوی دمای تنظیم شده باشد ، تغییری ایجاد نمی شود. کنترل دما در هر اتاق با یک جریان جداگانه با 200 میلی ثانیه تأخیر انجام می شود. کار اصلی با قطعه زیر انجام می شود:

اگر mHouse.HouseTemp< mHouse.MAX_TEMP = 5 Then Try

موضوع خواب (200)

Catch tie As ThreadlnterruptException

"انتظار غیرفعال قطع شده است

Catch e As Exception

"سایر موارد استثنائات را امتحان کنید

mHouse.HouseTemp + - 5 "و غیره

در زیر منبع کامل برنامه آمده است. نتیجه در شکل نشان داده شده است. 10.6: دمای خانه به 105 درجه فارنهایت (40.5 درجه سانتیگراد) رسیده است!

1 گزینه Strict On

2 سیستم واردات. موضوع

3 ماژول ماژول

4 زیر اصلی ()

5 Dim myHouse As New House (l0)

6 کنسول ReadLine ()

7 پایان ساب

8 ماژول پایان

9 خانه کلاس عمومی

10 سازه عمومی MAX_TEMP به عنوان عدد صحیح = 75

11 mCurTemp خصوصی به عنوان عدد صحیح = 55

12 اتاق خصوصی () As Room

13 Public Sub New (ByVal numOfRooms As Integer)

14 اتاق خواب ReDim (numOfRooms = 1)

15 Dim i As Integer

16 Dim aThreadStart As Threading.ThreadStart

17 Dim aThread As Thread

18 برای i = 0 به numOfRooms -1

19 سعی کنید

20 mRooms (i) = NewRoom (Me، mCurTemp، CStr (i) & "throom")

21 aThreadStart - جدید ThreadStart (آدرسOf _

mRooms (i) .CheckTempInRoom)

22 aThread = موضوع جدید (aThreadStart)

23 aThread.Start ()

24 E را به عنوان استثنا بگیرید

25 Console.WriteLine (E.StackTrace)

26 تلاش را پایان دهید

27 بعدی

28 پایان ساب

29 املاک عمومی HouseTemp () بصورت صحیح

سی گرفتن

31 بازگشت mCurTemp

32 دریافت کنید

33 مجموعه (ByVal Value As Integer)

34 mCurTemp = مقدار 35 مجموعه پایانی

36 ویژگی پایانی

37 پایان کلاس

38 اتاق کلاس عمومی

39 Private mCurTemp As Integer

40 نام خصوصی به عنوان رشته

41 mHouse خصوصی به عنوان خانه

42 Public Sub New (ByVal theHouse As House ،

ByVal temp As Integer، ByVal roomName As String)

43 mHouse = theHouse

44 mCurTemp = دما

45 mName = نام اتاق

46 پایان ساب

47 Sub Sub CheckTempInRoom ()

48 ChangeTemperature ()

49 پایان ساب

50 زیر خصوصی تغییر دما ()

51 امتحان کنید

52 اگر mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

53 موضوع خواب (200)

54 mHouse.HouseTemp + - 5

55 Console.WriteLine ("Am in" & Me.mName & _

56 ". دمای فعلی" & mHouse.HouseTemp)

57 Elself mHouse.HouseTemp< mHouse.MAX_TEMP Then

58 موضوع خواب (200)

59 mHouse.HouseTemp + = 1

60 Console.WriteLine ("Am in" & Me.mName & _

61 ". دمای فعلی" & mHouse.HouseTemp)

62 دیگر

63 Console.WriteLine ("Am in" & Me.mName & _

64 ". دمای فعلی" & mHouse.HouseTemp)

65 "هیچ کاری نکنید ، دما طبیعی است

66 پایان اگر

67 Catch tae As ThreadlnterlosedException

68 "انتظار غیرفعال قطع شد

69 Catch e As Exception

70 "سایر استثنائات

71 تلاش را پایان دهید

72 پایان ساب

73 پایان کلاس

برنج. 10.6 مسائل چند رشته ای

روش Sub Main (خطوط 4-7) یک "خانه" با ده "اتاق" ایجاد می کند. کلاس House حداکثر دمای 75 درجه فارنهایت (حدود 24 درجه سانتیگراد) را تعیین می کند. خطوط 13-28 یک سازنده خانه نسبتاً پیچیده را تعریف می کند. خطوط 18-27 کلید درک برنامه هستند. خط 20 یک شیء اتاق دیگر ایجاد می کند و یک اشاره به شیء خانه به سازنده منتقل می شود تا در صورت لزوم شیء اتاق بتواند به آن اشاره کند. خطوط 21-23 ده جریان را برای تنظیم درجه حرارت در هر اتاق شروع می کند. کلاس Room در خطوط 38-73 تعریف شده است. مرجع خانه coxpaدر متغیر mHouse در سازنده کلاس Room (خط 43) ذخیره می شود. کد بررسی و تنظیم دما (خطوط 50-66) ساده و طبیعی به نظر می رسد ، اما همانطور که به زودی خواهید دید ، این تصور فریبنده است! توجه داشته باشید که این کد در یک بلوک Try-Catch پیچیده شده است زیرا برنامه از روش Sleep استفاده می کند.

به سختی کسی موافق زندگی در دمای 105 درجه فارنهایت (40.5 تا 24 درجه سانتیگراد) است. چی شد؟ مشکل مربوط به خط زیر است:

اگر mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

و موارد زیر اتفاق می افتد: ابتدا ، درجه حرارت با جریان 1 بررسی می شود. او می بیند که درجه حرارت بسیار پایین است ، و آن را 5 درجه افزایش می دهد. متأسفانه ، قبل از افزایش دما ، جریان 1 قطع شده و کنترل به جریان 2 منتقل می شود. جریان 2 همان متغیری را بررسی می کند که هنوز تغییر نکرده استجریان 1. بنابراین ، جریان 2 نیز در حال آماده شدن برای افزایش درجه حرارت تا 5 درجه است ، اما وقت انجام این کار را ندارد و همچنین به حالت انتظار می رود. این روند ادامه می یابد تا جریان 1 فعال شود و به دستور بعدی - افزایش دما تا 5 درجه ادامه می دهد. این افزایش زمانی تکرار می شود که هر 10 جریان فعال شوند و ساکنان خانه اوقات بدی خواهند داشت.

راه حل مشکل: همگام سازی

در برنامه قبلی ، وضعیتی بوجود می آید که خروجی برنامه به ترتیب اجرای رشته ها بستگی دارد. برای خلاص شدن از شر آن ، باید مطمئن شوید که دستورات مانند

اگر mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then...

قبل از قطع شدن توسط نخ فعال به طور کامل پردازش می شوند. این ویژگی نامیده می شود شرم اتمی -یک بلوک کد باید توسط هر رشته بدون وقفه به عنوان یک واحد اتمی اجرا شود. گروهی از دستورات ، که در یک بلوک اتمی ترکیب شده اند ، تا زمانی که کامل نشوند ، توسط برنامه ریزی نخ قطع نمی شوند. هر زبان برنامه نویسی چند رشته ای روشهای خاص خود را برای اطمینان از اتمی بودن دارد. در VB .NET ، ساده ترین راه برای استفاده از دستور SyncLock این است که هنگام متغیر شیء هنگام فراخوانی وارد شود. تغییرات کوچکی در روش ChangeTemperature از مثال قبلی ایجاد کنید ، و برنامه خوب کار می کند:

Private Sub ChangeTemperature () SyncLock (mHouse)

تلاش كردن

اگر mHouse.HouseTemp< mHouse.MAXJTEMP -5 Then

موضوع خواب (200)

mHouse.HouseTemp + = 5

Console.WriteLine ("Am in" & Me.mName & _

". دمای فعلی" & mHouse.HouseTemp)

خود

mHouse.HouseTemp< mHouse. MAX_TEMP Then

موضوع. خواب (200) mHouse.HouseTemp + = 1

Console.WriteLine ("Am in" & Me.mName & _ ". دمای فعلی" & mHouse.HomeTemp) دیگر

Console.WriteLineC "Am in" & Me.mName & _ ". دمای فعلی" & mHouse.HouseTemp)

"هیچ کاری نکنید ، دما طبیعی است

End If Catch tie As ThreadlnterruptException را پایان دهید

"انتظار غیرفعال توسط Catch e As Exception قطع شد

"سایر استثنائات

پایان امتحان

پایان SyncLock

End Sub

کد بلوک SyncLock بصورت اتمی اجرا می شود. دسترسی به آن از همه موضوعات دیگر بسته می شود تا زمانی که اولین موضوع با دستور End SyncLock قفل را آزاد کند. اگر یک نخ در یک بلوک همگام به حالت انتظار غیرفعال برسد ، قفل باقی می ماند تا نخ قطع یا از سر گرفته شود.

استفاده صحیح از دستور SyncLock موضوع برنامه شما را ایمن نگه می دارد. متأسفانه ، استفاده بیش از حد از SyncLock تأثیر منفی بر عملکرد دارد. همگام سازی کد در یک برنامه چند رشته ای سرعت کار آن را چندین برابر کاهش می دهد. تنها مورد نیازترین کد را همگام سازی کرده و قفل را در اسرع وقت آزاد کنید.

کلاسهای مجموعه اصلی در برنامه های چند رشته ای ایمن نیستند ، اما .NET Framework شامل نسخه های ایمن برای اکثر کلاسهای مجموعه است. در این کلاس ها ، کد روش های بالقوه خطرناک در بلوک های SyncLock قرار داده شده است. نسخه های ایمن با موضوع کلاسهای جمع آوری باید در برنامه های چند رشته ای در هر کجا که یکپارچگی داده ها به خطر بیفتد مورد استفاده قرار گیرد.

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

شرایط کلاس عمومی متغیر

قفسه خصوصی مشترک به عنوان شی = شی جدید ()

mOK خصوصی به اشتراک گذاشته شده به عنوان مشترک بولی

Property TheConditionVariable () به عنوان Boolean

گرفتن

mOK را برگردانید

پایان دریافت

تنظیم (ByVal Value As Boolean) SyncLock (قفل)

mOK = ارزش

پایان SyncLock

پایان مجموعه

ویژگی پایان

پایان کلاس

کلاس فرمان و نظارت SyncLock

استفاده از دستور SyncLock شامل ظرافت هایی است که در مثال های ساده بالا نشان داده نشده است. بنابراین ، انتخاب شیء همگام سازی نقش بسیار مهمی ایفا می کند. سعی کنید برنامه قبلی را با دستور SyncLock (Me) به جای SyncLock (mHouse) اجرا کنید. دما دوباره از آستانه بالا می رود!

به یاد داشته باشید که دستور SyncLock با استفاده از همگام سازی می شود هدف - شی،به عنوان یک پارامتر منتقل می شود ، نه توسط قطعه کد. پارامتر SyncLock به عنوان دریچه ای برای دسترسی به قطعه هماهنگ شده از سایر نخ ها عمل می کند. دستور SyncLock (Me) در واقع چندین "در" مختلف را باز می کند ، این دقیقاً همان چیزی است که شما با همگام سازی از آن جلوگیری می کردید. اخلاق:

برای محافظت از داده های مشترک در یک برنامه چند رشته ای ، دستور SyncLock باید همزمان یک شی را همگام سازی کند.

از آنجایی که همگام سازی با یک شیء خاص مرتبط است ، در برخی شرایط ، می توان به طور ناخواسته قطعات دیگر را قفل کرد. فرض کنید شما دو روش همزمان ، اول و دوم دارید ، و هر دو روش بر روی شی bigLock همگام سازی شده اند. وقتی نخ 1 ابتدا متد را وارد می کند و bigLock را ضبط می کند ، هیچ رشته ای نمی تواند روش دوم را وارد کند ، زیرا دسترسی به آن از قبل به نخ 1 محدود شده است!

عملکرد دستور SyncLock را می توان زیر مجموعه ای از قابلیت های کلاس مانیتور در نظر گرفت. کلاس مانیتور بسیار قابل تنظیم است و می تواند برای حل وظایف همگام سازی بی اهمیت استفاده شود. دستور SyncLock آنالوگ تقریبی متدهای Enter و Exi t کلاس Moni tor است:

تلاش كردن

Monitor.Enter (theObject) در نهایت

مانیتور خروج (theObject)

پایان امتحان

برای برخی از عملیات استاندارد (افزایش / کاهش یک متغیر ، تبادل محتویات دو متغیر) ، .NET Framework کلاس Interlocked را ارائه می دهد ، که روش های آن این عملیات را در سطح اتمی انجام می دهد. با استفاده از کلاس Interlocked ، این عملیات بسیار سریعتر از استفاده از دستور SyncLock است.

به هم پیوستگی

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

شرایط را در نظر بگیرید به هم پیوستگی(بن بست) در ساده ترین شکل آن. دو برنامه نویس را روی میز شام تصور کنید. متأسفانه آنها فقط یک چاقو و یک چنگال برای دو نفر دارند. با فرض اینکه برای غذا خوردن به چاقو و چنگال نیاز دارید ، دو حالت ممکن است:

  • یکی از برنامه نویسان موفق به گرفتن چاقو و چنگال می شود و شروع به غذا خوردن می کند. وقتی سیر شد ، شام را کنار می گذارد و سپس برنامه نویس دیگری می تواند آنها را ببرد.
  • یک برنامه نویس چاقو را بر می دارد و دیگری چنگال را بر می دارد. هیچکدام نمی توانند شروع به خوردن کنند مگر اینکه دیگری دستگاه خود را رها کند.

در یک برنامه چند رشته ای ، این وضعیت نامیده می شود انسداد متقابلاین دو روش بر روی اجسام مختلف همزمان می شوند. موضوع A شیء 1 را گرفته و وارد قسمت برنامه محافظت شده توسط این شی می شود. متأسفانه ، برای کارکردن ، نیاز به دسترسی به کدی دارد که توسط Sync Lock دیگری با شیء همگام سازی متفاوت محافظت می شود. اما قبل از اینکه زمان لازم برای وارد کردن قطعه ای را داشته باشد که توسط یک شی دیگر هماهنگ شده است ، جریان B وارد آن می شود و این شی را ضبط می کند. اکنون نخ A نمی تواند وارد قطعه دوم شود ، نخ B نمی تواند وارد قطعه اول شود و هر دو نخ محکوم به انتظار نامحدود هستند. هیچ رشته ای نمی تواند به اجرا ادامه دهد زیرا شی مورد نیاز هرگز آزاد نمی شود.

تشخیص بن بست ها با این واقعیت پیچیده است که می تواند در موارد نسبتاً نادر رخ دهد. همه چیز بستگی به ترتیبی دارد که زمانبند زمان CPU را به آنها اختصاص می دهد. این امکان وجود دارد که در بیشتر موارد ، اشیاء همگام سازی به ترتیب بدون بن بست گرفته شوند.

موارد زیر پیاده سازی وضعیت بن بست است که توضیح داده شد. پس از بحث کوتاهی در مورد اساسی ترین نکات ، ما نحوه تشخیص وضعیت بن بست در پنجره موضوع را نشان خواهیم داد:

1 گزینه Strict On

2 سیستم واردات. موضوع

3 ماژول ماژول

4 زیر اصلی ()

5 دیم تام به عنوان برنامه نویس جدید ("تام")

6 دیم باب به عنوان برنامه نویس جدید ("باب")

7 Dim aThreadStart به عنوان ThreadStart جدید (آدرس Tom.Eat)

8 Dim aThread به عنوان موضوع جدید (aThreadStart)

9 aThread.Name = "تام"

10 Dim bThreadStart As New ThreadStarttAddressOf از Bob.Eat)

11 Dim bThread As New Thread (bThreadStart)

12 bThread.Name = "باب"

13 aThread.Start ()

14 bThread.Start ()

15 پایان ساب

16 پایان ماژول

17 چنگال کلاس عمومی

18 Private Shared mForkAvaiTable As Boolean = True

19 Private Shared mOwner As String = "Nobody"

20 ویژگی خواندنی خصوصی OwnsUtensil () As String

21 دریافت کنید

22 بازگشت مالک

23 پایان دریافت

24 ویژگی پایانی

25 Public Sub GrabForktByVal a As Programmer)

26 Console.Writel_ine (Thread.CurrentThread.Name & _

"تلاش برای گرفتن چنگال.")

27 Console.WriteLine (Me.OwnsUtensil & "دارای چنگال است."). ...

28 مانیتور. وارد کنید (من) "SyncLock (aFork)"

29 اگر mFork در دسترس است پس

30 a.HasFork = درست است

31 mOwner = a.MyName

32 mFork در دسترس = کاذب

33 Console.WriteLine (a.MyName & "just got the fork.waiting")

34 امتحان کنید

Thread.Sleep (100) Catch e As Exception Console.WriteLine (e.StackTrace)

پایان امتحان

35 پایان اگر

36 مانیتور خروج (من)

پایان SyncLock

37 پایان ساب

38 پایان کلاس

39 چاقو کلاس عمومی

40 Private Shared mKnifeAvailable As Boolean = True

41 Private Shared mOwner As String = "Nobody"

42 ویژگی خواندنی خصوصی OwnsUtensi1 () As String

43 دریافت کنید

44 بازگشت صاحبخانه

45 پایان دریافت

46 ویژگی پایانی

47 Public Sub GrabKnifetByVal a As Programmer)

48 Console.WriteLine (Thread.CurrentThread.Name & _

"تلاش برای گرفتن چاقو.")

49 Console.WriteLine (Me.OwnsUtensil & "دارای چاقو است.")

50 مانیتور. وارد کنید (من) "SyncLock (aKnife)"

51 اگر mKnifeAvailable سپس

52 mKnifeAvailable = غلط

53 a.HasKnife = درست است

54 mOwner = a.MyName

55 Console.WriteLine (a.MyName & "فقط چاقو را دیدم")

56 امتحان کنید

موضوع خواب (100)

Catch e As Exception

Console.WriteLine (e.StackTrace)

پایان امتحان

57 پایان اگر

58 مانیتور خروج (من)

59 پایان ساب

60 پایان کلاس

61 برنامه نویس کلاس عمومی

62 mName خصوصی به عنوان رشته

63 Private Shared mFork As Fork

64 Private Shared mKnife As Knife

65 خصوصی mHasKnife As Boolean

66 خصوصی mHasFork As Boolean

67 مشترک زیر جدید ()

68 mFork = چنگال جدید ()

69 mKnife = چاقوی جدید ()

70 پایان ساب

71 Public Sub New (ByVal theName As String)

72 mName = theName

73 پایان ساب

74 ویژگی خواندنی عمومی MyName () As String

75 دریافت کنید

76 بازگشت mName

77 پایان دریافت

78 ویژگی پایانی

79 اموال عمومی HasKnife () As Boolean

80 دریافت کنید

81 بازگشت mHasKnife

82 پایان دریافت

83 مجموعه (ByVal Value As Boolean)

84 mHasKnife = مقدار

85 پایان مجموعه

86 ویژگی پایانی

87 اموال عمومی HasFork () به عنوان Boolean

88 دریافت کنید

89 بازگشت mHasFork

90 پایان دریافت

91 مجموعه (ByVal Value As Boolean)

92 mHasFork = مقدار

93 پایان مجموعه

94 ویژگی پایانی

95 Public Sub Eat ()

96 Do Until Me.HasKnife And Me.HasFork

97 Console.Writeline (Thread.CurrentThread.Name & "در موضوع است.")

98 اگر Rnd ()< 0.5 Then

99 mFork.GrabFork (Me)

100 دیگر

101 mKnife.GrabKnife (من)

102 پایان اگر

103 حلقه

104 MsgBox (Me.MyName & "می توانید بخورید!")

105 mKnife = چاقوی جدید ()

106 mFork = چنگال جدید ()

107 End Sub

108 پایان کلاس

رویه اصلی Main (خطوط 4-16) دو نمونه از کلاس Programmer ایجاد می کند و سپس دو نخ را برای اجرای روش مهم Eat کلاس Programmer (خطوط 95-108) ، که در زیر توضیح داده شده است ، آغاز می کند. رویه اصلی نام رشته ها را تنظیم کرده و آنها را تنظیم می کند. احتمالاً هر اتفاقی که می افتد قابل درک و بدون اظهار نظر است.

کد کلاس Fork جالب تر به نظر می رسد (خطوط 17-38) (یک کلاس چاقو مشابه در خطوط 39-60 تعریف شده است). خطوط 18 و 19 مقادیر فیلدهای مشترک را مشخص می کند ، که با استفاده از آنها می توانید بفهمید که آیا در حال حاضر پلاگین موجود است یا خیر ، و در غیر این صورت ، چه کسی از آن استفاده می کند. خاصیت ReadOnly OwnUtensi1 (خطوط 20-24) برای ساده ترین انتقال اطلاعات در نظر گرفته شده است. در مرکز کلاس چنگال روش GrabFork "گرفتن چنگال" است ، که در خطوط 25-27 تعریف شده است.

  1. خطوط 26 و 27 به سادگی اطلاعات اشکال زدایی را روی کنسول چاپ می کنند. در کد اصلی روش (خطوط 28-36) ، دسترسی به چنگال توسط شی همزمان می شودکمربند من از آنجا که برنامه ما فقط از یک چنگال استفاده می کند ، Me sync تضمین می کند که هیچ دو رشته نمی توانند آن را به طور همزمان بگیرند. دستور Slee "p (در بلوک شروع شده از خط 34) تاخیر بین گرفتن چنگال / چاقو و شروع غذا را شبیه سازی می کند. توجه داشته باشید که دستور Sleep قفل اجسام را باز نمی کند و فقط به بن بست ها سرعت می بخشد!
    با این حال ، جالب ترین کد کلاس Programmer (خطوط 61-108) است. خطوط 67-70 یک سازنده عمومی تعریف می کند تا اطمینان حاصل شود که فقط یک چنگال و چاقو در برنامه وجود دارد. کد ملک (خطوط 74-94) ساده است و نیازی به اظهار نظر ندارد. مهمترین اتفاق در روش Eat رخ می دهد که توسط دو نخ جداگانه اجرا می شود. این روند به صورت حلقه ای ادامه می یابد تا جایی که جریان چنگال را به همراه چاقو می گیرد. در خطوط 98-102 ، جسم به طور تصادفی چنگال / چاقو را با استفاده از فراخوانی Rnd می گیرد ، که همین امر باعث بن بست می شود. موارد زیر اتفاق می افتد:
    رشته ای که روش Eat of the Tot را اجرا می کند فراخوانی شده و وارد حلقه می شود. چاقو را می گیرد و به حالت انتظار می رود.
  2. نخ اجرا کننده روش Bob's Eat فراخوانی شده و وارد حلقه می شود. نمی تواند چاقو را بگیرد ، اما چنگال را گرفته و به حالت آماده باش می رود.
  3. رشته ای که روش Eat of the Tot را اجرا می کند فراخوانی شده و وارد حلقه می شود. او سعی می کند چنگال را بگیرد ، اما باب قبلاً چنگال را گرفته است. نخ به حالت انتظار می رود
  4. نخ اجرا کننده روش Bob's Eat فراخوانی شده و وارد حلقه می شود. او سعی می کند چاقو را بگیرد ، اما چاقو قبلاً توسط شی Thoth گرفته شده است. نخ به حالت انتظار می رود

همه اینها به طور نامحدود ادامه دارد - ما با یک وضعیت معمولی بن بست روبرو هستیم (سعی کنید برنامه را اجرا کنید ، خواهید دید که هیچ کس نمی تواند از این طریق غذا بخورد).
همچنین می توانید بررسی کنید که آیا بن بست در پنجره موضوعات رخ داده است یا خیر. برنامه را اجرا کرده و با کلیدهای Ctrl + Break قطع کنید. متغیر Me را در نمای نمایش گنجانده و پنجره streams را باز کنید. نتیجه شبیه چیزی است که در شکل نشان داده شده است. 10.7 از شکل مشخص است که نخ باب چاقویی گرفته است ، اما چنگال ندارد. در پنجره Threads در خط Tot راست کلیک کرده و دستور Switch to Thread را از منوی زمینه انتخاب کنید. نمای نما نشان می دهد که جریان Thoth دارای چنگال است ، اما چاقو ندارد. البته ، این صد در صد اثبات نیست ، اما چنین رفتاری حداقل باعث می شود شک کنید که چیزی اشتباه بوده است.
اگر گزینه همگام سازی توسط یک شیء (مانند برنامه با افزایش دما در خانه) امکان پذیر نیست ، برای جلوگیری از قفل های متقابل ، می توانید اشیاء همگام سازی را شماره گذاری کرده و همیشه آنها را به ترتیب ثابت ضبط کنید. بیایید با قیاس برنامه نویس غذاخوری ادامه دهیم: اگر نخ همیشه چاقو را ابتدا و سپس چنگال را بگیرد ، هیچ مشکلی در قفل شدن وجود نخواهد داشت. اولین جریانی که چاقو را می گیرد می تواند به طور معمول غذا بخورد. ترجمه شده به زبان جریان برنامه ، بدین معنی است که گرفتن شی 2 تنها در صورتی امکان پذیر است که شی 1 برای اولین بار ضبط شود.

برنج. 10.7 تجزیه و تحلیل بن بست در پنجره موضوع

بنابراین ، اگر تماس Rnd را در خط 98 حذف کرده و قطعه را جایگزین آن کنیم

mFork.GrabFork (من)

mKnife.GrabKnife (من)

بن بست از بین می رود!

هنگام ایجاد داده ها با آنها همکاری کنید

در برنامه های چند رشته ای ، اغلب شرایطی وجود دارد که نخ ها نه تنها با داده های مشترک کار می کنند ، بلکه منتظر می مانند تا ظاهر شوند (یعنی نخ 1 باید قبل از استفاده از نخ 2 از داده ها استفاده کند). از آنجا که داده ها به اشتراک گذاشته می شوند ، دسترسی به آنها باید همزمان شود. همچنین لازم است وسایلی برای اطلاع از موضوعات منتظر در مورد ظاهر داده های آماده ارائه شود.

این وضعیت معمولاً نامیده می شود مشکل تامین کننده / مصرف کنندهموضوع سعی می کند به داده هایی که هنوز وجود ندارند دسترسی پیدا کند ، بنابراین باید کنترل را به نخ دیگری منتقل کند که داده های مورد نیاز را ایجاد می کند. مشکل با کد زیر حل می شود:

  • موضوع 1 (مصرف کننده) از خواب بیدار می شود ، یک روش هماهنگ شده را وارد می کند ، به دنبال داده ها می گردد ، آنها را پیدا نمی کند و به حالت انتظار می رود. بطور مقدماتیاز نظر فیزیکی ، او باید انسداد را برداشته تا در کار نخ تأمین کننده تداخل ایجاد نکند.
  • موضوع 2 (ارائه دهنده) یک روش هماهنگ شده را آزاد می کند که توسط موضوع 1 آزاد می شود ، ایجاد می کندداده ها برای جریان 1 و به نوعی جریان 1 را در مورد وجود داده ها مطلع می کند. سپس قفل را رها می کند تا نخ 1 بتواند داده های جدید را پردازش کند.

سعی نکنید این مشکل را با فراخوانی مداوم موضوع 1 و بررسی وضعیت یک متغیر شرطی که مقدار آن توسط رشته 2 تنظیم شده است ، حل کنید. این تصمیم بر عملکرد برنامه شما تأثیر جدی می گذارد ، زیرا در بیشتر موارد موضوع 1 بدون هیچ گونه فراخوانی دلیل؛ و نخ 2 آنقدر منتظر می ماند که زمان ایجاد داده ها تمام می شود.

روابط ارائه دهنده / مصرف کننده بسیار رایج است ، بنابراین بدوی اولیه برای چنین موقعیت هایی در کتابخانه های کلاس برنامه نویسی چند رشته ای ایجاد می شود. در NET ، این موارد اولیه Wait و Pulse-PulseAl 1 نامیده می شوند و بخشی از کلاس Monitor هستند. شکل 10.8 وضعیتی را که قرار است برنامه ریزی کنیم نشان می دهد. این برنامه سه صف موضوع را ترتیب می دهد: صف انتظار ، صف مسدود کردن و صف اجرا. زمانبند موضوع ، CPU را به موضوعاتی که در صف انتظار هستند اختصاص نمی دهد. برای اینکه یک نخ زمان اختصاص داده شود ، باید به صف اجرا منتقل شود. در نتیجه ، کار برنامه بسیار کارآمدتر از رای گیری معمول یک متغیر شرطی است.

در شبه کد ، اصطلاح مصرف کننده داده به شرح زیر فرموله شده است:

"ورود به یک بلوک همگام از نوع زیر

در حالی که هیچ داده ای وجود ندارد

به صف انتظار بروید

حلقه

اگر داده وجود دارد ، آن را پردازش کنید.

بلوک همگام را ترک کنید

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

در شبه کد ، اصطلاح ارائه دهنده داده به این شکل است:

"ورود به یک بلوک نمای همگام

در حالی که داده ها مورد نیاز نیست

به صف انتظار بروید

سایر داده ها را تولید کنید

هنگامی که داده ها آماده است ، با Pulse-PulseAll تماس بگیرید.

برای انتقال یک یا چند موضوع از صف مسدود کردن به صف اجرا. بلوک همگام را ترک کنید (و به صف اجرا برگردید)

فرض کنید برنامه ما یک خانواده را با یکی از والدین پولساز و فرزندی که این پول را خرج می کند شبیه سازی می کند. وقتی پول تمام شدمعلوم می شود که کودک باید منتظر رسیدن مقدار جدید باشد. پیاده سازی نرم افزاری این مدل به شکل زیر است:

1 گزینه Strict On

2 سیستم واردات. موضوع

3 ماژول ماژول

4 زیر اصلی ()

5 Dim theFamily As New Family ()

6 theFamily.StartltsLife ()

7 پایان ساب

8 پایان فیودول

9

10 خانواده کلاس عمومی

11 mMoney Private As Integer

12 mWeek خصوصی به عنوان عدد صحیح = 1

13 Public Sub StartltsLife ()

14 Dim aThreadStart As New ThreadStarUAddressOf Me.Produce)

15 Dim bThreadStart As New ThreadStarUAddressOf Me. مصرف کنید)

16 Dim aThread به عنوان موضوع جدید (aThreadStart)

17 Dim bThread As New Thread (bThreadStart)

18 aThread.Name = "تولید"

19 aThread.Start ()

20 bThread.Name = "مصرف"

21 b موضوع شروع ()

22 پایان ساب

23 اموال عمومی TheWeek () بصورت صحیح

24 دریافت کنید

25 هفته بازگشت

26 دریافت کنید

27 مجموعه (ByVal Value As Integer)

هفته 28 - ارزش

29 مجموعه پایان

30 ویژگی پایانی

31 اموال عمومی OurMoney () بصورت صحیح

32 دریافت کنید

33 بازگشت پول

34 پایان دریافت

35 مجموعه (ByVal Value As Integer)

36 میلیون پول = ارزش

37 مجموعه پایان

38 ویژگی پایانی

39 زیرمجموعه عمومی ()

40 موضوع خواب (500)

41 انجام دهید

42 مانیتور. وارد کنید (من)

43 Do while Me.OurMoney> 0

44 مانیتور صبر کنید (من)

45 حلقه

46 Me.OurMoney = 1000

47 مانیتور. نبض همه (من)

48 مانیتور خروج (من)

49 حلقه

50 پایان ساب

51 مصرف عمومی فرعی ()

52 MsgBox ("در موضوع مصرف هستم")

53 انجام دهید

54 مانیتور. وارد کنید (من)

55 Do while Me.OurMoney = 0

56 مانیتور صبر کنید (من)

57 حلقه

58 Console.WriteLine ("پدر و مادر عزیز من تمام هزینه های شما را خرج کردم" & _

پول در هفته "& TheWeek)

59 هفته + = 1

60 اگر TheWeek = 21 * 52 سپس System.Environment.Exit (0)

61 Me.OurMoney = 0

62 مانیتور. پالس همه (من)

63 مانیتور خروج (من)

64 حلقه

65 پایان ساب

66 پایان کلاس

روش StartltsLife (خطوط 13-22) آماده شروع جریانهای تولید و مصرف است. مهمترین اتفاق در جریان های Produce (خطوط 39-50) و مصرف (خطوط 51-65) رخ می دهد. روش Sub Produce میزان دسترسی پول را بررسی می کند و در صورت وجود پول ، به صف انتظار می رود. در غیر این صورت ، والدین پول تولید می کنند (خط 46) و اشیاء در صف انتظار را در مورد تغییر وضعیت مطلع می کند. توجه داشته باشید که فراخوانی Pulse-Pulse All فقط زمانی فعال می شود که قفل با دستور Monitor.Exit آزاد شود. برعکس ، روش Sub Consume در دسترس بودن پول را بررسی می کند و اگر پولی وجود نداشته باشد ، والدین انتظار را در مورد آن مطلع می کند. خط 60 به سادگی برنامه را پس از 21 سال مشروط خاتمه می دهد. سیستم تماس Environment.Exit (0) آنالوگ .NET فرمان End است (دستور End نیز پشتیبانی می شود ، اما بر خلاف System. Environment. خروج ، کد خروجی را به سیستم عامل بر نمی گرداند).

موضوعاتی که در صف انتظار قرار می گیرند باید توسط بخشهای دیگر برنامه شما آزاد شوند. به همین دلیل است که ما ترجیح می دهیم از PulseAll بر Pulse استفاده کنیم. از آنجا که از قبل مشخص نیست که در هنگام فراخوانی Pulse 1 کدام نخ فعال می شود ، اگر تعداد نسبتاً کوتاهی در صف وجود داشته باشد ، می توانید PulseAll را نیز به همین ترتیب فراخوانی کنید.

چند رشته ای در برنامه های گرافیکی

بحث ما در مورد چند رشته ای در برنامه های GUI با مثالی شروع می شود که توضیح می دهد چند رشته ای در برنامه های GUI برای چیست. همانطور که در شکل نشان داده شده است ، با دو دکمه Start (btnStart) و Cancel (btnCancel) یک فرم ایجاد کنید. 10.9 با کلیک روی دکمه شروع ، یک کلاس ایجاد می شود که شامل یک رشته تصادفی از 10 میلیون کاراکتر و یک روش برای شمارش وقایع حرف "E" در آن رشته طولانی است. به استفاده از کلاس StringBuilder برای ایجاد کارآمدتر رشته های طولانی توجه کنید.

مرحله 1

موضوع 1 متوجه می شود که هیچ داده ای برای آن وجود ندارد. Wait را صدا می کند ، قفل را رها می کند و به صف انتظار می رود.



گام 2

هنگامی که قفل آزاد می شود ، نخ 2 یا نخ 3 از صف بلوک خارج می شود و وارد یک بلوک همگام می شود و قفل را بدست می آورد.

مرحله 3

فرض کنید نخ 3 وارد یک بلوک همگام شده ، داده ایجاد می کند و Pulse-Pulse All را فرا می خواند.

بلافاصله پس از خروج از بلوک و آزاد کردن قفل ، نخ 1 به صف اجرا منتقل می شود. اگر نخ 3 Pluse را صدا می زند ، فقط یکی وارد صف اجرا می شودthread ، وقتی Pluse All فراخوانی می شود ، همه نخ ها به صف اجرا می روند.



برنج. 10.8 مشکل ارائه دهنده / مصرف کننده

برنج. 10.9 چند رشته ای در یک برنامه GUI ساده

سیستم واردات. متن

شخصیت های تصادفی کلاس عمومی

m_Data خصوصی به عنوان StringBuilder

mjength خصوصی ، m_count به عنوان عدد صحیح

Public Sub New (ByVal n As Integer)

m_ طول = n -1

m_Data = New StringBuilder (m_length) MakeString ()

End Sub

زیر شاخه خصوصی MakeString ()

Dim i As Integer

Dim myRnd As New Random ()

برای i = 0 تا m_length

"ایجاد یک عدد تصادفی بین 65 تا 90 ،

"آن را به حروف بزرگ تبدیل کنید

"و به شی StringBuilder متصل شوید

m_Data.Append (Chr (myRnd.Next (65.90)))

بعد

End Sub

Public Sub StartCount ()

GetEes ()

End Sub

ساب خصوصی GetEes ()

Dim i As Integer

برای i = 0 تا m_length

اگر m_Data.Chars (i) = CChar ("E") سپس

m_count + = 1

پایان اگر بعدی

m_CountDone = درست است

End Sub

عمومی فقط خواندنی

Property GetCount () As Integer Get

اگر نه (m_CountDone) پس

m_count را برگردانید

پایان اگر

End Get End ویژگی

عمومی فقط خواندنی

Property IsDone () همانطور که بولی دریافت می کنید

برگشت

m_CountDone

پایان دریافت

ویژگی پایان

پایان کلاس

یک کد بسیار ساده در ارتباط با دو دکمه روی فرم وجود دارد. روش btn-Start_Click به کلاس RandomCharacters فوق اشاره می کند که یک رشته با 10 میلیون کاراکتر را در بر می گیرد:

Private Sub btnStart_Click (فرستنده ByVal به عنوان System.Object.

ByVal e As System.EventArgs) دسته btnSTart. کلیک کنید

Dim RC As New RandomCharacters (10000000)

RC.StartCount ()

MsgBox ("تعداد es ها" و RC.GetCount)

End Sub

دکمه لغو یک کادر پیام نمایش می دهد:

Private Sub btnCancel_Click (فرستنده ByVal As System.Object._

ByVal e As System.EventArgs) دسته btn لغو. کلیک کنید

MsgBox ("شمارش قطع شد!")

End Sub

هنگامی که برنامه اجرا می شود و دکمه Start فشار داده می شود ، معلوم می شود که دکمه Cancel به ورودی کاربر پاسخ نمی دهد زیرا حلقه پیوسته مانع از عملکرد دکمه در صورت دریافت رویداد می شود. این در برنامه های مدرن غیرقابل قبول است!

دو راه حل ممکن وجود دارد. اولین گزینه ، که به خوبی از نسخه های قبلی VB شناخته شده است ، چند رشته ای را کنار می گذارد: تماس DoEvents در حلقه گنجانده شده است. در NET ، این دستور به شکل زیر است:

برنامه. DoEvents ()

در مثال ما ، این قطعاً مطلوب نیست - چه کسی می خواهد برنامه ای را با ده میلیون تماس DoEvents کند کند! اگر به جای آن حلقه را به یک موضوع جداگانه اختصاص دهید ، سیستم عامل بین نخ ها سوئیچ می کند و دکمه Cancel عملکردی باقی می ماند. پیاده سازی با یک موضوع جداگانه در زیر نشان داده شده است. برای اینکه به وضوح نشان دهیم که دکمه Cancel کار می کند ، وقتی روی آن کلیک می کنیم ، به سادگی برنامه را خاتمه می دهیم.

مرحله بعدی: دکمه نمایش تعداد

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

برنج. 10.10 فرم دکمه قفل شده

انتظار می رود یک موضوع جداگانه شمارش را انجام داده و دکمه غیرقابل دسترسی را باز کند. البته این کار را می توان انجام داد ؛ علاوه بر این ، چنین کاری اغلب پیش می آید. متأسفانه ، شما نمی توانید به روشنی ترین روش عمل کنید - پیوند ثانویه را با نگه داشتن پیوند به دکمه ShowCount در سازنده یا حتی استفاده از یک نماینده استاندارد ، به موضوع GUI پیوند دهید. به عبارت دیگر، هرگزاز گزینه زیر استفاده نکنید (اساسی اشتباهخطوط پررنگ هستند)

شخصیت های تصادفی کلاس عمومی

m_0ata خصوصی As StringBuilder

m_CountDone به عنوان بولی

mjength خصوصی. m_count As Integer

m_Button خصوصی به عنوان Windows.Forms.Button

Public Sub New (ByVa1 n As Integer، _

ByVal b به عنوان Windows.Forms.Button)

m_length = n - 1

m_Data = New StringBuilder (mJength)

m_Button = b MakeString ()

End Sub

زیر شاخه خصوصی MakeString ()

Dim I As Integer

Dim myRnd As New Random ()

برای I = 0 تا m_length

m_Data.Append (Chr (myRnd.Next (65.90)))

بعد

End Sub

Public Sub StartCount ()

GetEes ()

End Sub

ساب خصوصی GetEes ()

Dim I As Integer

برای I = 0 تا mjength

اگر m_Data.Chars (I) = CChar ("E") سپس

m_count + = 1

پایان اگر بعدی

m_CountDone = درست است

m_Button.Enabled = درست است

End Sub

عمومی فقط خواندنی

Property GetCount () بصورت صحیح

گرفتن

اگر نه (m_CountDone) پس

استثناء جدیدی را پرتاب کنید ("هنوز شمارش انجام نشده است") در غیر این صورت

m_count را برگردانید

پایان اگر

پایان دریافت

ویژگی پایان

Public Readyly Property IsDone () به صورت بولی

گرفتن

بازگشت m_CountDone

پایان دریافت

ویژگی پایان

پایان کلاس

به احتمال زیاد این کد در برخی موارد کار می کند. با این اوصاف:

  • تعامل نخ ثانویه با نخ ایجاد کننده GUI قابل سازماندهی نیست واضحبه معنای.
  • هرگزعناصر موجود در برنامه های گرافیکی را از جریان برنامه های دیگر تغییر ندهید. همه تغییرات فقط باید در رشته ای که GUI ایجاد کرده است رخ دهد.

اگر این قوانین را زیر پا بگذارید ، ما تضمین می کنیماشکالات ظریف و ظریف در برنامه های گرافیکی چند رشته ای شما رخ می دهد.

همچنین امکان سازماندهی تعامل اشیا با استفاده از رویدادها وجود نخواهد داشت. کارگر 06 رویداد بر روی همان موضوعی که RaiseEvent نامیده می شود اجرا می شود تا رویدادها به شما کمک نکند.

با این حال ، عقل سلیم حکم می کند که برنامه های گرافیکی باید وسیله ای برای اصلاح عناصر از یک رشته دیگر داشته باشند. در چارچوب NET ، یک روش ایمن برای فراخوانی روش های برنامه های GUI از یک موضوع دیگر وجود دارد. نوع خاصی از نماینده متد Invoker از System.Windows برای این منظور استفاده می شود. تشکیل می دهد. قطعه زیر نسخه جدیدی از روش GetEes را نشان می دهد (خطوط تغییر یافته به صورت برجسته):

ساب خصوصی GetEes ()

Dim I As Integer

برای I = 0 تا m_length

اگر m_Data.Chars (I) = CChar ("E") سپس

m_count + = 1

پایان اگر بعدی

m_CountDone = تلاش واقعی

Dim mylnvoker به عنوان روش جدیدlnvoker (AddressOf UpDateButton)

myInvoker.Invoke () Catch e As ThreadlnterruptException

"شکست

پایان امتحان

End Sub

زیرنویس عمومی UpDateButton ()

m_Button.Enabled = درست است

End Sub

تماسهای بین رشته ای با دکمه نه مستقیماً ، بلکه از طریق Method Invoker انجام می شود. .NET Framework تضمین می کند که این گزینه برای نخ امن است.

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

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

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

با این حال ، یک دلیل اساسی تر دیگر نیز وجود دارد: این روزها برنامه نویسان به ندرت در اسمبلر برنامه ریزی می کنند یا حداقل به خروجی جدا شده کامپایلر نگاه می کنند. در غیر این صورت ، برای آنها بسیار ساده تر خواهد بود که به این ایده عادت کنند که ده ها دستورالعمل مونتاژ می تواند مربوط به یک دستور زبان سطح بالا (مانند VB .NET) باشد. نخ می تواند پس از هر یک از این دستورالعمل ها قطع شود و بنابراین در وسط یک فرمان سطح بالا قرار می گیرد.

اما این همه ماجرا نیست: کامپایلرهای مدرن عملکرد برنامه را بهینه می کنند و سخت افزار کامپیوتر می تواند در مدیریت حافظه اختلال ایجاد کند. در نتیجه ، کامپایلر یا سخت افزار می توانند ترتیب دستورات مشخص شده در سورس کد برنامه را بدون اطلاع شما تغییر دهند [ بسیاری از کامپایلرها کپی چرخه ای آرایه ها را مانند i = 0 تا n: b (i) = a (i): ncxt بهینه می کنند. کامپایلر (یا حتی یک مدیر حافظه تخصصی) می تواند به سادگی یک آرایه ایجاد کرده و سپس آن را به جای کپی عناصر فردی به دفعات زیاد ، با یک عملیات تک تک پر کند!].

امیدوارم این توضیحات به شما کمک کند تا بهتر بفهمید که چرا برنامه نویسی چند رشته ای مشکلات زیادی ایجاد می کند - یا حداقل از رفتار عجیب برنامه های چند رشته ای شما غافلگیر نمی شود!

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

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

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

مثال ادعا نمی کند که کامل است ، فقط ساده ترین روش های تعامل بین رشته ها را نشان می دهد. اجازه دادن به کاربر برای "کور کردن سریع" (که می داند چقدر از آن متنفرم) یک برنامه چند رشته ای که به درستی کار می کند.
در آن ، همه چیز با جزئیات (به نظر من) توضیح داده شده است ، اما اگر سوالی دارید ، بپرسید.
اما بار دیگر به شما هشدار می دهم: جریان آسان نیست... اگر نمی دانید چگونه همه چیز کار می کند ، یک خطر بزرگ وجود دارد که اغلب همه چیز برای شما خوب کار می کند ، و گاهی اوقات برنامه بیش از حد عجیب رفتار می کند. رفتار یک برنامه چند رشته ای نادرست نوشته شده بستگی زیادی به تعداد زیادی از عوامل دارد که گاهی اوقات در حین اشکال زدایی قابل تکرار نیستند.

بنابراین یک مثال. برای سهولت ، هم کد را قرار داده و هم بایگانی را با ماژول و کد فرم ضمیمه کرده ام

واحد ExThreadForm؛

استفاده می کند
Windows ، Messages ، SysUtils ، Variants ، Classs ، Graphics ، Controls ، Forms ،
گفتگوها ، StdCtrls ؛

// ثابت هایی که هنگام انتقال داده ها از یک جریان به یک فرم با استفاده از
// ارسال پیام های پنجره
const
WM_USER_SendMessageMetod = WM_USER + 10 ؛
WM_USER_PostMessageMetod = WM_USER + 11؛

نوع
// شرح کلاس موضوع ، از فرزندان tThread
tMyThread = کلاس (tThread)
خصوصی
SyncDataN: عدد صحیح ؛
SyncDataS: رشته ؛
روش SyncMetod1 ؛
حفاظت شده
روش اجرا ؛ نادیده گرفتن
عمومی
پارامتر 1: رشته ؛
پارام 2: عدد صحیح ؛
پارام 3: بولی ؛
متوقف شد: بولی
LastRandom: Integer؛
IterationNo: Integer؛
ResultList: tStringList؛

Constructor Create (aParam1: String) ؛
destructor نابود کردن؛ نادیده گرفتن
پایان؛

// توصیف کلاس فرم با استفاده از جریان
TForm1 = کلاس (TForm)
برچسب 1: TLabel؛
یادداشت 1: TMemo ؛
btnStart: TButton؛
btnStop: TButton؛
ویرایش 1: TEdit ؛
ویرایش 2: TEdit ؛
CheckBox1: TCheckBox ؛
برچسب 2: TLabel؛
برچسب 3: TLabel؛
برچسب 4: TLabel؛
روش btnStartClick (فرستنده: TObject) ؛
روش btnStopClick (فرستنده: TObject) ؛
خصوصی
(اظهارنامه خصوصی)
MyThread: tMyThread؛
رویه EventMyThreadOnTerminate (فرستنده: tObject) ؛
رویه EventOnSendMessageMetod (var Msg: TMessage) ؛ پیام WM_USER_SendMessageMetod ؛
رویه EventOnPostMessageMetod (var Msg: TMessage) ؛ پیام WM_USER_PostMessageMetod؛

عمومی
(اعلامیه های عمومی)
پایان؛

var
فرم 1: TForm1 ؛

{
متوقف - انتقال داده ها را از یک فرم به یک جریان نشان می دهد.
همگام سازی اضافی مورد نیاز نیست ، زیرا ساده است
نوع تک کلمه ای ، و فقط توسط یک موضوع نوشته می شود.
}

رویه TForm1.btnStartClick (فرستنده: TObject) ؛
شروع
تصادفی سازی () ؛ // اطمینان از تصادفی بودن توالی توسط Random () - هیچ ربطی به جریان ندارد

// یک نمونه از شی جریان ایجاد کنید ، یک پارامتر ورودی به آن ارسال کنید
{
توجه!
سازنده جریان به گونه ای نوشته شده است که جریان ایجاد می شود
به صورت مجاز معلق می شود:
1. لحظه راه اندازی آن را کنترل کنید. این تقریبا همیشه راحت تر است زیرا
به شما امکان می دهد حتی قبل از شروع ، جریان را راه اندازی کنید ، ورودی آن را ارسال کنید
پارامترها و غیره
2. چون سپس پیوند به شی ایجاد شده در قسمت فرم ذخیره می شود
پس از تخریب خود نخ (به پایین مراجعه کنید) که وقتی نخ در حال کار است
ممکن است در هر زمان رخ دهد ، این پیوند معتبر نمی شود.
}
MyThread: = tMyThread.Create (Form1.Edit1.Text) ؛

// با این حال ، از آنجا که موضوع ایجاد شد به حالت تعلیق درآمده است ، پس در صورت وجود هرگونه خطا
// در حین راه اندازی اولیه (قبل از شروع) ، باید خودمان آن را از بین ببریم
// برای آنچه ما از try / გარდა block استفاده می کنیم
تلاش كردن

// تخصیص یک کنترل کننده پایان بخش موضوع که در آن دریافت خواهیم کرد
// نتایج کار جریان ، و پیوند به آن "رونویسی" شود
MyThread.OnTerminate: = EventMyThreadOnTerminate؛

// از آنجا که نتایج در OnTerminate جمع آوری می شود ، به عنوان مثال قبل از تخریب خود
// جریان سپس نگرانی های از بین بردن آن را برطرف می کنیم
MyThread.FreeOnTerminate: = درست؛

// نمونه ای از عبور پارامترهای ورودی از طریق فیلدهای شی جریان ، در نقطه
// زمانی که هنوز در حال اجرا نیستید ، فوراً عمل کنید.
// شخصاً ، ترجیح می دهم این کار را از طریق پارامترهای لغو شده انجام دهم
// سازنده (tMyThread.Create)
MyThread.Param2: = StrToInt (Form1.Edit2.Text) ؛

MyThread.Stopped: = False؛ // نوعی پارامتر نیز ، اما در آن تغییر می کند
// زمان اجرای موضوع
جز
// از آنجا که موضوع هنوز شروع نشده است و نمی تواند خود را تخریب کند ، ما آن را "دستی" از بین می بریم
FreeAndNil (MyThread) ؛
// و سپس اجازه دهید استثنا طبق معمول اداره شود
بالا بردن ؛
پایان؛

// از آنجا که شی موضوع با موفقیت ایجاد و پیکربندی شده است ، زمان شروع آن فرا رسیده است
MyThread.Resume؛

ShowMessage ("جریان شروع شد") ؛
پایان؛

روش TForm1.btnStopClick (فرستنده: TObject) ؛
شروع
// اگر نمونه موضوع هنوز وجود دارد ، از آن بخواهید متوقف شود
// و دقیقاً "بپرس". در اصل ، ما همچنین می توانیم "مجبور" کنیم ، اما این کار را می کند
// گزینه فوق العاده اضطراری ، که نیاز به درک واضح از همه اینها دارد
// جریان آشپزخانه. بنابراین ، در اینجا مورد توجه قرار نمی گیرد.
اگر Assigned (MyThread) پس
MyThread.Stopped: = درست است
دیگری
ShowMessage ("موضوع اجرا نمی شود!") ؛
پایان؛

روش TForm1.EventOnSendMessageMetod (var Msg: TMessage) ؛
شروع
// روش پردازش پیام همزمان
// در WParam آدرس شی tMyThread ، در LParam مقدار فعلی LastRandom موضوع
با tMyThread (Msg.WParam) شروع می شود
Form1.Label3.Caption: = قالب ("٪ d٪ d٪ d"،)؛
پایان؛
پایان؛

روش TForm1.EventOnPostMessageMetod (var Msg: TMessage) ؛
شروع
// روش برای مدیریت یک پیام ناهمزمان
// در WParam مقدار فعلی IterationNo ، در LParam مقدار فعلی جریان LastRandom
Form1.Label4.Caption: = قالب ("٪ d٪ d" ،)؛
پایان؛

روش TForm1.EventMyThreadOnTerminate (فرستنده: tObject) ؛
شروع
// مهم!
// روش مدیریت رویداد OnTerminate همیشه در زمینه اصلی فراخوانی می شود
// thread - این توسط پیاده سازی tThread تضمین می شود. بنابراین ، در آن شما می توانید آزادانه
// از هرگونه ویژگی و روش هر شی استفاده کنید

// در هر صورت ، مطمئن شوید که نمونه شی هنوز وجود دارد
اگر تعیین نشده است (MyThread) ، سپس خروج کنید ؛ // اگر آنجا نباشد ، هیچ کاری نمی توان انجام داد

// نتایج کار موضوع مورد نمونه شیء موضوع را دریافت کنید
Form1.Memo1.Lines.Add (قالب ("جریان با نتیجه٪ d به پایان رسید")) ؛
Form1.Memo1.Lines.AddStrings ((فرستنده به عنوان tMyThread) .ResultList)؛

// مرجع نمونه جریان شی را از بین ببرید.
// از آنجا که موضوع ما خود تخریب می شود (FreeOnTerminate: = True)
// پس از تکمیل کنترل کننده OnTerminate ، نمونه جریان شیء خواهد بود
// از بین می رود (رایگان) و همه مراجع به آن نامعتبر می شوند.
// برای اینکه تصادفاً به چنین پیوندی برخورد نکنید ، MyThread را بازنویسی کنید
// بار دیگر ، من توجه می کنم - ما شیء را از بین نمی بریم ، بلکه فقط پیوند را بازنویسی می کنیم. یک شیء
// خودش را نابود کند!
MyThread: = نیل ؛
پایان؛

سازنده tMyThread.Create (aParam1: String) ؛
شروع
// ایجاد یک نمونه از جریان تعلیق شده (هنگام مشاهده نمونه نظر را ببینید)
به ارث برده Create (True) ؛

// ایجاد اشیاء داخلی (در صورت لزوم)
ResultList: = tStringList.Create؛

// دریافت اطلاعات اولیه

// داده های ورودی منتقل شده از پارامتر را کپی کنید
Param1: = aParam1؛

// نمونه ای از دریافت داده های ورودی از اجزای VCL در سازنده یک شی جریان
// این در این مورد قابل قبول است ، زیرا سازنده در زمینه فراخوانی می شود
// موضوع اصلی بنابراین ، می توان به اجزای VCL در اینجا دسترسی پیدا کرد.
// اما ، من این را دوست ندارم ، زیرا فکر می کنم وقتی موضوعی چیزی می داند بد است
// درباره نوعی از آنجا اما ، چه کاری نمی توانید برای تظاهرات انجام دهید.
Param3: = Form1.CheckBox1.Checked ؛
پایان؛

destructor tMyThread.Destroy؛
شروع
// تخریب اجسام داخلی
FreeAndNil (ResultList) ؛
// پایه tThread را از بین ببرید
به ارث برده؛
پایان؛

روش tMyThread.Execute ؛
var
t: کاردینال ؛
s: رشته ؛
شروع
شماره تکرار: = 0؛ // شمارنده نتایج (شماره چرخه)

// در مثال من ، بدن نخ یک حلقه است که به پایان می رسد
// یا توسط یک "درخواست" خارجی برای خاتمه دادن از طریق پارامتر متغیر Stopped ،
// یا فقط با انجام 5 حلقه
// نوشتن این مطلب از طریق یک حلقه "ابدی" برای من خوشایندتر است.

در حالی که True do آغاز می شود

Inc (IterationNo)؛ // شماره چرخه بعدی

LastRandom: = تصادفی (1000) ؛ // شماره کلید - برای نشان دادن انتقال پارامترها از جریان به فرم

T: = تصادفی (5) +1 ؛ // زمانی که در صورت کامل نشدن به خواب خواهیم رفت

// کار احمقانه (بسته به پارامتر ورودی)
اگر نه Param3 پس
Inc (Param2)
دیگری
دسامبر (پارام 2) ؛

// یک نتیجه متوسط ​​ایجاد کنید
s: = قالب ("٪ s٪ 5d٪ s٪ d٪ d" ،
);

// یک نتیجه متوسط ​​را به لیست نتایج اضافه کنید
ResultList.Add (s)؛

//// نمونه هایی از انتقال یک نتیجه متوسط ​​به یک فرم

//// عبور از روش همگام - روش کلاسیک
//// معایب:
//// - متدی که همگام سازی می شود معمولاً متدی از کلاس جریان است (برای دسترسی
//// به فیلدهای شی جریان) ، اما برای دسترسی به فیلدهای فرم ، باید
//// "بدان" در مورد آن و زمینه های آن (اشیاء) ، که معمولاً خیلی خوب نیست
//// نقطه نظر سازمان برنامه.
//// - موضوع فعلی تا پایان اجرا به حالت تعلیق در می آید
//// روش همگام سازی.

//// مزایای:
//// - استاندارد و همه کاره
//// - در یک روش همزمان ، می توانید استفاده کنید
//// همه زمینه های شی جریان.
// ابتدا ، در صورت لزوم ، باید داده های منتقل شده را ذخیره کنید
// زمینه های خاص شیء شی.
SyncDataN: = IterationNo؛
SyncDataS: = "همگام سازی" + s؛
// و سپس فراخوانی متد همزمان را ارائه دهید
همگام سازی (SyncMetod1) ؛

//// ارسال از طریق ارسال پیام همزمان (SendMessage)
//// در این مورد ، داده ها را می توان از طریق پارامترهای پیام (LastRandom) منتقل کرد ،
//// و از طریق فیلدهای شیء ، آدرس نمونه را در پارامتر پیام ارسال کنید
//// شی جریان - عدد صحیح (خود).
//// معایب:
//// - موضوع باید دسته پنجره فرم را بداند
//// - مانند Synchronize ، موضوع فعلی تا زمانی تعلیق می شود
//// پایان پردازش پیام توسط نخ اصلی
//// - به مقدار قابل توجهی از زمان CPU برای هر تماس نیاز دارد
//// (برای تغییر موضوع) بنابراین تماس مکرر نامطلوب است
//// مزایای:
//// - مانند Synchronize ، هنگام پردازش پیام ، می توانید از آن استفاده کنید
//// همه فیلدهای شی جریان (البته ، اگر آدرس آن وارد شده باشد)


//// تاپیک را راه اندازی کنید
SendMessage (Form1.Handle ، WM_USER_SendMessageMetod ، Integer (Self) ، LastRandom) ؛

//// انتقال از طریق ارسال پیام ناهمزمان (PostMessage)
//// از آنجا که در این مورد ، تا زمانی که پیام توسط موضوع اصلی دریافت می شود ،
//// جریان ارسال ممکن است قبلاً تکمیل شده باشد و آدرس نمونه را ارسال کند
//// شی شی معتبر نیست!
//// معایب:
//// - موضوع باید دسته پنجره فرم را بداند.
//// - به دلیل ناهمگونی ، انتقال داده ها فقط از طریق پارامترها امکان پذیر است
//// پیامها ، که انتقال داده هایی که دارای اندازه هستند را بطور قابل توجهی پیچیده می کند
//// بیش از دو کلمه ماشین استفاده از آن برای عبور صحیح و غیره راحت است.
//// مزایای:
//// - بر خلاف روش های قبلی ، موضوع فعلی NOT
//// متوقف شده و بلافاصله اجرا را از سر می گیرد
//// - برخلاف تماس هماهنگ شده ، کنترل کننده پیام
//// یک روش فرم است که باید از شی جریان آگاهی داشته باشد ،
//// یا در صورتی که داده ها فقط منتقل شوند ، هیچ چیز در مورد جریان نمی دانید
//// از طریق پارامترهای پیام یعنی ممکن است نخ چیزی در مورد شکل نداند.
//// به طور کلی - فقط Handle او ، که قبلا می تواند به عنوان پارامتر منتقل شود
//// تاپیک را راه اندازی کنید
PostMessage (Form1.Handle ، WM_USER_PostMessageMetod ، IterationNo ، LastRandom) ؛

//// برای اتمام احتمالی را بررسی کنید

// برای تکمیل بر اساس پارامتر بررسی کنید
اگر متوقف شده است ، سپس شکستن ؛

// گاهی اوقات تکمیل را بررسی کنید
اگر IterationNo> = 10 سپس Break ؛

خواب (t * 1000) ؛ // t ثانیه بخوابید
پایان؛
پایان؛

روش tMyThread.SyncMetod1 ؛
شروع
// این روش از طریق روش Synchronize فراخوانی می شود.
// یعنی ، علیرغم این واقعیت که یک روش از موضوع tMyThread است ،
// در زمینه موضوع اصلی برنامه اجرا می شود.
// بنابراین ، او می تواند هر کاری را انجام دهد ، خوب ، یا تقریباً همه چیز :)
// اما به یاد داشته باشید ، ارزش "درهم و برهم کردن" طولانی مدت در اینجا را ندارد

// پارامترهای منتقل شده ، می توانیم از فیلدهای مخصوص ، جایی که آنها را داریم ، استخراج کنیم
// قبل از تماس ذخیره می شود.
Form1.Label1.Caption: = SyncDataS ؛

// یا از دیگر زمینه های شی جریان ، به عنوان مثال ، منعکس کننده وضعیت فعلی آن است
Form1.Label2.Caption: = قالب ("٪ d٪ d" ،) ؛
پایان؛

به طور کلی ، قبل از مثال ، استدلال زیر در مورد موضوع ارائه شد ...

اولا:
مهمترین قانون برنامه نویسی چند رشته ای در دلفی به شرح زیر است:
در زمینه یک موضوع غیر اصلی ، دسترسی به خواص و روش های فرم ها و در واقع تمام اجزایی که از tWinControl "رشد" می کنند غیرممکن است.

این بدان معناست (تا حدی ساده شده) که نه در روش اجرا که از TThread به ارث برده است و نه در سایر روشها / رویه ها / عملکردهایی که از Execute نامیده می شوند ، ممنوع استدسترسی مستقیم به هرگونه ویژگی و روش اجزای بصری.

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

به طور خلاصه در انگشتان دست خود:

اغلب ، یک برنامه چند رشته ای یا زمانی انجام می شود که لازم است نوعی کار طولانی مدت انجام شود ، یا زمانی که می توان همزمان چندین کار را انجام داد که پردازنده را بار زیادی نمی کند.

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

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

موارد دیگری نیز وجود دارد ، اما کمتر. با این حال ، این مهم نیست. اکنون در مورد آن نیست.

حالا ، چگونه همه نوشته شده است. به طور طبیعی ، یک مورد شایع ترین ، تا حدی کلی ، در نظر گرفته می شود. بنابراین.

کارهایی که در یک موضوع جداگانه انجام می شود ، در حالت کلی ، دارای چهار نهاد است (نمی دانم چگونه می توان آن را دقیق تر نامید):
1. داده های اولیه
2. در واقع خود کار (ممکن است به داده های اولیه بستگی داشته باشد)
3. داده های متوسط ​​(به عنوان مثال ، اطلاعات در مورد وضعیت فعلی اجرای کار)
4. داده های خروجی (نتیجه)

بیشتر اوقات ، اجزای بصری برای خواندن و نمایش بیشتر داده ها استفاده می شود. اما ، همانطور که در بالا ذکر شد ، شما نمی توانید مستقیماً به اجزای بصری جریان دسترسی داشته باشید. چگونه بودن؟
توسعه دهندگان دلفی پیشنهاد می کنند از روش Synchronize کلاس TThread استفاده کنید. در اینجا من نحوه استفاده از آن را توضیح نمی دهم - مقاله فوق برای این کار وجود دارد. بگذارید فقط بگویم که کاربرد آن ، حتی کاربرد صحیح ، همیشه موجه نیست. دو مشکل وجود دارد:

اول ، بدنه متدی که از طریق Synchronize نامیده می شود همیشه در زمینه نخ اصلی اجرا می شود و بنابراین ، در حین اجرا ، حلقه پیام پنجره دوباره اجرا نمی شود. بنابراین ، باید به سرعت اجرا شود ، در غیر این صورت ، همه مشکلات مشابه با اجرای یک رشته را خواهیم داشت. در حالت ایده آل ، روشی که از طریق Synchronize نامیده می شود ، عموماً فقط باید برای دسترسی به خواص و روشهای اشیاء بصری مورد استفاده قرار گیرد.

ثانیاً ، اجرای یک روش از طریق همگام سازی به دلیل نیاز به دو سوئیچ بین نخ ها ، یک لذت "گران" است.

علاوه بر این ، هر دو مشکل به هم متصل هستند و باعث ایجاد تناقض می شوند: از یک سو ، برای حل اولین مورد ، باید روش هایی را که از طریق Synchronize نامیده می شوند "خرد کنید" ، و از سوی دیگر ، آنها باید بیشتر تماس گرفته شوند و ارزش خود را از دست بدهند. منابع پردازنده

بنابراین ، مانند همیشه ، لازم است که منطقی برخورد شود ، و در موارد مختلف ، از روشهای متفاوتی از تعامل جریان با دنیای خارج استفاده کنید:

اطلاعات اولیه
تمام داده هایی که به جریان منتقل می شوند و در حین عملکرد تغییر نمی کنند ، باید حتی قبل از شروع به انتقال ، به عنوان مثال ، هنگام ایجاد جریان برای استفاده از آنها در بدنه یک موضوع ، باید یک کپی محلی از آنها تهیه کنید (معمولاً در زمینه های نسل TThread).
اگر داده های اولیه وجود دارد که می توانند در حین اجرای موضوع تغییر کنند ، باید از طریق روشهای همگام سازی (روشهایی که از طریق Synchronize نامیده می شوند) یا از طریق زمینه های موضوع موضوع (از نسل TThread) به چنین داده هایی دسترسی داشته باشید. مورد دوم نیاز به احتیاط دارد.

داده های میانی و خروجی
در اینجا ، دوباره ، چندین روش وجود دارد (به ترجیح من):
- روش ارسال ناهمزمان پیامها به پنجره اصلی برنامه.
معمولاً برای ارسال پیام در مورد پیشرفت روند به پنجره اصلی برنامه ، با انتقال مقدار کمی داده (به عنوان مثال ، درصد تکمیل) استفاده می شود.
- روش ارسال همزمان پیام ها به پنجره اصلی برنامه.
معمولاً برای اهداف مشابه ارسال ناهمزمان استفاده می شود ، اما به شما امکان می دهد حجم بیشتری از داده ها را بدون ایجاد کپی جداگانه منتقل کنید.
- روشهای همزمان ، در صورت امکان ، ترکیب انتقال داده ها تا حد ممکن به یک روش.
همچنین می تواند برای بازیابی اطلاعات از یک فرم استفاده شود.
- از طریق زمینه های شیء جریان ، دسترسی متقابل متقابل را فراهم می کند.
جزئیات بیشتر را می توان در مقاله یافت.

هه مدت کوتاهی نتیجه نداد