Кіріспе

Бірлестік ұғымы. Жоспарлаушы тәуелсіз басқара алатын бағдарламалық нұсқаулардың ең кіші тізбегі.

Компьютерлік ғылымда орындалу тізбегі – жоспарлаушы тәуелсіз басқара алатын бағдарламалық нұсқаулардың ең кіші тізбегі, ол әдетте операциялық жүйенің бір бөлігі болып табылады. Көп жағдайда, тізбек – процесс құрамының бір бөлігі. Белгілі бір процестің бірнеше тізбегі бір мезгілде (көп тізбектілік мүмкіндіктері арқылы) орындалуы мүмкін, жад сияқты ресурстарды бөліседі, ал әртүрлі процестер мұндай ресурстарды бөліспейді. Атап айтқанда, процестің тізбектері кез келген уақытта орындалатын кодын, динамикалық түрде бөлінген айнымалыларының мәндерін және тізбекке жатпайтын жаһандық айнымалыларды бөліседі. Тізбектер мен процестерді іске асыру операциялық жүйелерге байланысты әртүрлі болады. Қазіргі операциялық жүйелерде Таненбаум процестерді ұйымдастырудың көптеген әртүрлі модельдері мүмкін екенін көрсетеді.

Тарих

Сызықтар 1967 жылы OS/360 көп бағдарламалау жүйесінде (MVT) "тапсырмалар" деген атпен алғаш пайда болды. Солтцер (1966) Виктор А. Высоцкийді "сызық" терминін енгізгені үшін атады. Бағдарламалық қолданбаларда сызықтарды қолдану 2000-шы жылдардың басында CPU процессорлары көп ядролы бола бастаған кезде жиірек кездесті. Көп ядролы процессорлардың мүмкіндіктерін толық пайдаланып, өнімділікті арттыруды көздеген қолданбалар, бір уақытта бірнеше операцияны орындау қабілетін (параллелизмді) пайдалануға мәжбүр болды.

Қарым-қатынас ұғымдары

Жоспарлау ядро деңгейінде немесе пайдаланушы деңгейінде жүзеге асырылуы мүмкін, ал көп тапсырмалылық алдын ала немесе өзара іс-қимыл арқылы орындалуы мүмкін. Бұл осыған байланысты әртүрлі түсініктерді тудырады.

Процестер

Ядро деңгейінде процесс бір немесе бірнеше ядролық жіптерді қамтиды, олар процестің ресурстарын бөліседі, мысалы, жад және файл дескрипторларын – процесс ресурстардың бірлігі, ал жіп – жоспарлау және орындаудың бірлігі. Ядролық жоспарлау әдетте біркелкі түрде алдын ала немесе, сирек жағдайларда, өзара іс-қимыл арқылы жүзеге асырылады. Пайдаланушы деңгейінде, орындалу ортасы сияқты процесс, өзі бірнеше орындалу жіптерін жоспарлай алады. Егер олар Erlang-тағыдай деректермен бөліспесе, олар әдетте процестер деп аталады, ал деректермен бөліссе, олар әдетте (пайдаланушы) жіптер деп аталады, әсіресе алдын ала жоспарланған болса. Өзара іс-қимыл арқылы жоспарланған пайдаланушы жіптері талшықтар деп аталады; әртүрлі процестер пайдаланушы жіптерін әртүрлі жоспарлай алады. Пайдаланушы жіптері ядролық жіптермен әртүрлі тәсілдермен орындалуы мүмкін (бірге-бірге, көпке-бірге, көпке-көпке). "Жеңіл салмақты процесс" термині әртүрлі жағдайларда пайдаланушы жіптерін немесе пайдаланушы жіптерін ядролық жіптерге жоспарлауға арналған ядролық механизмдерді білдіреді. Процесс – ядролық жоспарлаудың "ауыр салмақты" бірлігі, себебі процестерді құру, жою және ауыстыру салыстырмалы түрде қымбат. Процестер операциялық жүйемен бөлінген ресурстарға ие. Ресурстарға жад (код және деректер үшін), файл дескрипторлары, сокеттер, құрылғы дескрипторлары, терезелер және процесс басқару блогы кіреді. Процестер процестік оқшаулау арқылы оқшауланады және файл дескрипторларын немесе ортақ жад сегменттерін мұралау немесе бір файлды ортақ түрде карталау сияқты нақты әдістер арқылы ғана адрестік кеңістіктерді немесе файлдық ресурстарды бөліспейді – процестер аралық байланысқа қараңыз. Процес құру немесе жою салыстырмалы түрде қымбат, себебі ресурстарды алу немесе босату қажет. Процестер әдетте алдын ала көп тапсырмалы болып табылады, ал процесс ауыстыру контекстік ауыстырудың қарапайым шығынынан басқа, кэшті тазалау сияқты мәселелерге байланысты өте қымбат, (әсіресе процесс ауыстыру виртуалды жад адрестемесін өзгертеді, бұл жарамсыздыққа және осылайша белгіленбеген аудармалық буфердің (TLB), әсіресе x86-да) тазалануына әкеледі).

Жәндіктер желілері

Ядролық жіп – ядролық жоспарлаудың "жеңіл" бірлігі. Әрбір процесс кем дегенде бір ядролық жіпті қамтиды. Егер бір процесс ішінде бірнеше ядролық жіп болса, олар бірдей жад және файлдық ресурстарды бөліседі. Операциялық жүйенің процесс жоспарлаушысы алдын ала болса, ядролық жіптер алдын ала көп тапсырмалы болып жұмыс істейді. Ядролық жіптер тек стек, тіркелімдердің көшірмесі (бағдарлама санағышы және жіпке тән жергілікті сақтау қоса алғанда, егер бар болса) ғана иеленеді, сондықтан оларды құру және жою салыстырмалы түрде арзан. Жіптерді ауыстыру да салыстырмалы түрде арзан: ол контексттік ауыстыруды (тіркелімдерді және стек көрсеткішін сақтау және қалпына келтіру) қажет етеді, бірақ виртуалды жадты өзгерте алмайды, сондықтан кэш үшін қолайлы (TLB жарамды күйінде қалады). Ядро процессордың әрбір ядросына бір немесе бірнеше бағдарламалық жіптерді тағайындауы мүмкін (ол көп жіпті қолдауына байланысты өзіне бірнеше бағдарламалық жіптерді тағайындауға қабілетті), және тоқтап қалған жіптерді ауыстыра алады. Дегенмен, ядролық жіптерді ауыстыру пайдаланушы жіптерін ауыстыруға қарағанда әлдеқайда көп уақыт алады.

Пайдаланушылар желілері

Кейде жіптер пайдаланушы кеңістігіндегі кітапханаларда іске асырылады, сондықтан олар пайдаланушы жіптері деп аталады. Ядро оларды білмейді, демек олар пайдаланушы кеңістігінде басқарылады және жоспарланады. Кейбір іске асырулар көппроцессорлы машиналардан тиімді пайдалану үшін пайдаланушы жіптерін бірнеше ядро жіптерінің үстіне құрайды (M:N моделі). Виртуалды машиналар іске асырған пайдаланушы жіптері жасыл жіптер деп те аталады. Пайдаланушы жіптерін іске асыру әдетте толығымен пайдаланушы кеңістігінде болғандықтан, бір процестегі пайдаланушы жіптері арасындағы контексттік ауысу өте тиімді, себебі ол ядромен ешқандай өзара әрекеттесуді қажет етпейді: ағымдағы орындалып жатқан пайдаланушы жіпі немесе талшығы қолданатын процессор тіркегіштерін жергілікті түрде сақтап, орындалуға тиіс пайдаланушы жіпі немесе талшығына қажетті тіркегіштерді жүктеу арқылы контексттік ауысу жүзеге асырылады. Жоспарлау пайдаланушы кеңістігінде жүргізілетіндіктен, жоспарлау саясатын бағдарламаның жұмыс жүктемесінің талаптарына оңай бейімдеуге болады. Дегенмен, пайдаланушы жіптерінде (ядро жіптеріне қарағанда) тоқтатушы жүйелік шақыруларды пайдалану мәселелі болуы мүмкін. Егер пайдаланушы жіпі немесе талшығы тоқтатушы жүйелік шақыруды орындаса, процестегі басқа пайдаланушы жіптері мен талшықтары жүйелік шақыру аяқталғанша орындала алмайды. Мұның бір мысалы – кіріс-шығыс операцияларын орындау кезінде: көптеген бағдарламалар кіріс-шығысты синхронды түрде орындау үшін жазылған. Кіріс-шығыс операциясы басталғанда жүйелік шақыру жасалады және кіріс-шығыс операциясы аяқталғанша қайтарылмайды. Осы уақыт аралығында бүкіл процесс ядромен «тоқтатылады» және орындала алмайды, бұл сол процестегі басқа пайдаланушы жіптері мен талшықтарының орындалуына кедергі келтіреді. Бұл мәселенің кең таралған шешімі (әсіресе көптеген жасыл жіптерді іске асыруларда қолданылады) – кіріс-шығыс API-ін ұсыну, ол шақырушы жіпті емес, бүкіл процесті тоқтатушы интерфейсті іске асырады, кіріс-шығысты ішкі түрде тоқтатусыз жүзеге асырып, кіріс-шығыс операциясы жүріп жатқанда басқа пайдаланушы жіпін немесе талшығын жоспарлайды. Басқа тоқтатушы жүйелік шақырулар үшін де ұқсас шешімдер ұсынылуы мүмкін. Сонымен қатар, бағдарлама синхронды кіріс-шығысты немесе басқа тоқтатушы жүйелік шақыруларды (әсіресе, тоқтатусыз кіріс-шығысты пайдалану арқылы, соның ішінде лямбда жалғастырулары және/немесе асинхронды/күту примитивтерін) пайдаланудан аулақ болу үшін жазылуы мүмкін.

Талшықтар

Талшықтар – бұл кооперативті жоспарлаудың одан да жеңіл бірлігі: басқа талшықты іске қосу үшін жұмыс істеп жатқан талшық міндетті түрде "бас тартуы" керек, бұл оларды ядролық немесе пайдаланушы жіптеріне қарағанда әлдеқайда оңайрақ іске асыруға мүмкіндік береді. Талшықты бір процестегі кез келген жіпте іске қосуға болады. Бұл қолданбаларға ядролық жоспарлауға сүйенудің орнына (қолданбаға қарай реттеуге келмейтін) өздері жоспарлауды басқару арқылы өнімділікті арттыруға мүмкіндік береді. OpenMP сияқты параллель бағдарламалау орталары кейде өз тапсырмаларын талшықтар арқылы іске асырады. Корутиналар мен талшықтар тығыз байланысты, ерекшелігі корутиналар тіл деңгейіндегі құрылым, ал талшықтар жүйе деңгейіндегі құрылым болып табылады.

Алдын ала және бірлесіп жоспарлау

Операциялық жүйелер жіптерді алдын ала немесе өзара келісім бойынша жоспарлайды. Көп пайдаланушы операциялық жүйелер әдетте контексттік ауысу арқылы орындалу уақытын дәл бақылау үшін алдын ала көп жіпті басқаруды қолдайды. Дегенмен, алдын ала жоспарлау бағдарламашылар күтпеген сәттерде жіптерді контексттік ауысуға алып келуі мүмкін, соның салдарынан құлыптау конвойы, басымдық инверсиясы немесе басқа да қосымша әсерлер туындауы мүмкін. Ал, өзара келісім бойынша көп жіптілік жіптердің орындалу бақылауын өз еркімен беруіне тәуелді, осылайша жіптердің толық аяқталуын қамтамасыз етеді. Бұл, егер өзара келісім бойынша көп тапсырмалы жіп ресурс күтіп тұрып тоқтап қалса немесе қарқынды есептеу кезінде орындалу бақылауын бермей, басқа жіптерді ашарса, проблемалар тудыруы мүмкін.

Бір немесе бірнеше процессорлы жүйелер

2000-шы жылдардың басына дейін көптеген үстел компьютерлері тек бір ядролы процессорға ие болды және аппараттық жіптерді қолдамады. Дегенмен, жіптер осындай компьютерлерде де қолданыла берді, себебі жіптер арасындағы ауысу, әдетте, толық процестік контексттік ауыстырулардан жылдам болатын. 2002 жылы Intel Pentium 4 процессорына гипертілемдеу (hyper-threading) деп аталатын бір мезгілде бірнеше жіпті өңдеу мүмкіндігін қосты; 2005 жылы олар екі ядролы Pentium D процессорын, ал AMD екі ядролы Athlon 64 X2 процессорын ұсынды. Бір процессорлы жүйелер көп жіптілікті әдетте уақыт бөлу арқылы іске асырады: орталық процессор (CPU) әртүрлі бағдарламалық жіптер арасында ауысады. Бұл контексттік ауыстыру көбінесе жиі болады, сондықтан пайдаланушылар жіптерді немесе тапсырмаларды параллель түрде орындалып жатқандай сезінеді (көптеген серверлік/үстел операциялық жүйелерде, басқа жіптер күтіп тұрғанда, бір жіпке берілетін уақыт мөлшері көбінесе 100–200 мс-қа шектеледі). Көп процессорлы немесе көп ядролы жүйеде бірнеше жіп параллель түрде орындалуы мүмкін, әр процессор немесе ядро бір мезгілде жеке жіпті өңдейді; аппараттық жіптері бар процессор немесе ядрода жеке бағдарламалық жіптер сонымен қатар жеке аппараттық жіптер арқылы орындалуы мүмкін.

1:1 (желі деңгейіндегі желілік)

Пайдаланушы ядродағы жоспарланатын объектілермен жеке сәйкестікте (1:1) жасайтын жіптер ең қарапайым жіптерді іске асыру болып табылады. OS/2 және Win32 бастапқыда осы тәсілді қолданды, ал Linux-та GNU C кітапханасы осы тәсілді іске асырады (NPTL немесе ескі LinuxThreads арқылы). Solaris, NetBSD, FreeBSD, macOS және iOS да осы тәсілді пайдаланады.

M:1 (пайдаланушы деңгейіндегі желілік байланыс)

M:1 моделі қолданба деңгейіндегі барлық жіптер ядро деңгейіндегі бір жоспарланған нысанға шамаланғанын білдіреді; FreeBSD 5 M:N моделін іске асырды. FreeBSD 6 1:1 және M:N модельдерін қолдады, пайдаланушылар /etc/libmap.conf файлы арқылы берілген бағдарлама үшін қайсысын пайдалану керектігін таңдай алатын болды. FreeBSD 7 нұсқасынан бастап 1:1 әдепкіге айналды. FreeBSD 8 енді M:N моделін қолдамайды.

Бір желілі және көп желілі бағдарламалар

Компьютерлік бағдарламалауда бір жіпті өңдеу – бір уақытта бір команданы өңдеу. Айтқандардың семантикасы мен процестің күйін формалды түрде талдау кезінде, бір жіптілік термині функционалдық бағдарламалау қауымдастығында жиі қолданылатын "бір жіп ішінде кері іздеу" мағынасында да қолданылуы мүмкін. Көп жіптілік көбінесе көп тапсырмалы операциялық жүйелерде кездеседі. Көп жіптілік – бір процесте бірнеше жіптің болуына мүмкіндік беретін кең таралған бағдарламалау және орындалу моделі. Бұл жіптер процестің ресурстарын бөліседі, бірақ тәуелсіз түрде орындала алады. Жіптік бағдарламалау моделі әзірлеушілерге бір уақытта орындалудың пайдалы абстракциясын ұсынады. Көп жіптілік бір процеске параллель орындалуды қамтамасыз ету үшін де қолданылуы мүмкін, әсіресе көп процессорлы жүйелерде. Көп жіптілік кітапханалары әдетте жаңа жіп құру үшін функцияны шақыруды ұсынады, ол функцияны параметр ретінде қабылдайды. Содан кейін, берілген функцияны орындауды бастайтын және функция аяқталғанда тоқтатылатын бір уақыттағы жіп құрылады. Жіптік кітапханалар деректерді синхрондау функцияларын да ұсынады.

Тақырыптар мен деректерді қадамдастыру

Бір процестегі жіптер бірдей адрестік кеңістікті бөліседі. Бұл бір уақытта орындалатын кодты тығыз байланыстырып, деректерді IPC-нің қосымша шығындары мен күрделілігінсіз ыңғайлы түрде алмасуға мүмкіндік береді. Дегенмен, жіптер арасында ортақ пайдаланғанда, тіпті қарапайым дерек құрылымдары да жаңарту үшін бірнеше CPU нұсқауларын қажет етсе, жарыс жағдайларына ұшырауы мүмкін: екі жіп дерек құрылымын бір уақытта жаңартуға тырысып, оның күтпеген жерден өзгеретінін байқауы мүмкін. Жарыс жағдайларынан туындаған қателерді көбейту және оқшаулау өте қиын болуы мүмкін. Мұны болдырмау үшін жіптік қолданбалық бағдарламалау интерфейстері (API) дерек құрылымдарын бір мезгілде қол жеткізуден қорғау үшін мутекстер сияқты синхрондау примитивтерін ұсынады. Бір процессорлы жүйелерде бұғатталған мутекске тап болған жіп ұйықтап, осылайша контексттік ауысуды тудыруы керек. Көп процессорлы жүйелерде жіп спинлоктағы мутекске бұрылуы мүмкін. Бұл екеуі де өнімділікті төмендетіп, симметриялық мультипроцессорлық (SMP) жүйелердегі процессорларды жад шинасы үшін бәсекелестіруге мәжбүр етеді, әсіресе құлыптаудың дәлдігі тым жоғары болса. Басқа синхрондау API-леріне жағдай айнымалылары, маңызды бөлімдер, семафорлар және мониторлар кіреді.

Сым жинағы

Тақырыптарды қолданатын танымал бағдарламалау әдісі – тақырыптар қоры. Онда бағдарлама басталып іске қосылғанда белгілі бір саны тақырыптар құрылады да, тапсырма берілуін күтеді. Жаңа тапсырма келгенде, тақырып оянады, тапсырманы аяқтап, қайта күту күйіне көшеді. Бұл әрбір тапсырма орындалған сайын салыстырмалы түрде қымбатқа түсетін тақырыптарды құру және жою процесінен сақтайды, сондай-ақ тақырыптарды басқаруды бағдарламашыдан алып, оны тақырыптарды басқаруды оңтайландыруға арналған кітапханаға немесе операциялық жүйеге жүктейді.

Бағдарламалау тілдерін қолдау

Көптеген бағдарламалау тілдері белгілі бір деңгейде жіптерді қолдайды. IBM PL/I(F) 1960-шы жылдардың соңында көп жіптілік (көп тапсырмалылық деп аталады) қолдауын қамтыды, және бұл Optimizing Compiler және кейінгі нұсқаларда жалғастырылды. IBM Enterprise PL/I компиляторы жаңа модельді "жіп" API енгізді. Бұл нұсқалардың ешқайсысы да PL/I стандартының құрамына кірмеді. C және C++ көптеген іске асырулары жіптерді қолдайды және операциялық жүйенің жіптік API-леріне қол жеткізуді қамтамасыз етеді. Жіптерді іске асырудың стандартталған интерфейсі - POSIX жіптері (Pthreads), бұл C функциялары кітапханасының шақырулары жиынтығы. Жүйе әкімшілері интерфейсті қалағанша іске асыруға еркін, бірақ қолданба әзірлеушісі бірнеше платформада бірдей интерфейсті пайдалана білуі керек. Unix платформаларының көпшілігі, соның ішінде Linux, Pthreads-ті қолдайды. Microsoft Windows-тың процесінде өзінің жіптік функциялары бар, h интерфейсі көп жіптілік үшін, мысалы, beginthread. Кейбір жоғары деңгейдегі (әдетте платформааралық) бағдарламалау тілдері, мысалы Java, Python және .NET Framework тілдері, жіптік іске асырудағы платформаға тән айырмашылықтарды абстракциялап, жіптерді әзірлеушілерге ұсынады. Басқа бірнеше бағдарламалау тілдері мен тілдік кеңейтулер де бір мезгілдестік пен жіптер концепциясын әзірлеушіден толықтай абстракциялауға тырысады (Cilk, OpenMP, Message Passing Interface (MPI)). Кейбір тілдер қатарластырылған өңдеуге арналған, жіптерді немесе бір мезгілдестікті қажет етпейді (әсіресе GPU-ды пайдалану). Бірнеше интерпретацияланған бағдарламалау тілдерінде (мысалы, Ruby үшін Ruby MRI, Python үшін CPython) жіптер мен бір мезгілде орындалуды қолдайтын, бірақ жіптердің қатар орындалмауын қолдайтын жағдай бар, бұл жаһандық интерпретатордың құлпынан (GIL) туындайды. GIL - интерпретатордың қолдауындағы өзара құлыптау, ол интерпретатордың бір мезгілде екі немесе одан да көп жіпте қолданба кодын бір мезгілде түсіндіруіне кедергі келтіреді. Бұл көп ядролы жүйелердегі қатарластырылған өңдеуді шектейді. Ол сонымен қатар процессорға тәуелді жіптердің (процессорды қажет ететін) өнімділігін шектейді, бірақ I/O немесе желілік жіптерге әсері аз. Интерпретацияланған бағдарламалау тілдерінің басқа іске асырылуы, мысалы, Thread кеңейтуін пайдаланатын Tcl, GIL шектемесін Apartment моделін пайдалану арқылы болдырмайды, онда деректер мен кодтар жіптер арасында нақты түрде "бөлісуі" керек. Tcl-де әр жіпте бір немесе бірнеше интерпретатор бар. CUDA сияқты деректермен қатар есептеу үшін жасалған бағдарламалау модельдерінде, жіптер массиві жадыдағы деректерді табу үшін тек оның идентификаторын пайдалана отырып, бірдей кодты қатар орындайды. Негізінде, қолданба әр жіп әр түрлі жад сегменттерінде бірдей операцияны орындайтындай етіп жасалуы керек, осылайша олар қатар жұмыс істей алады және GPU архитектурасын пайдалана алады. Verilog сияқты аппараттық сипаттама тілдерінде өте көп сандағы жіптерді қолдайтын (жабдықты модельдеу үшін) басқа жіптік модель бар.