Питхон Тхреадинг: Увод – вдзвдз

U ovom uputstvu istražićemo kako se koristi ugrađeni Python modul za rad sa nitima, kako bismo otkrili mogućnosti višenitnog programiranja u Pythonu.

Počevši od osnova procesa i niti, razumećemo kako višenitnost funkcioniše u Pythonu, razmatrajući koncepte konkurentnosti i paralelizma. Nakon toga, naučićemo kako pokrenuti jednu ili više niti u Pythonu, koristeći modul ugrađen za obradu niti.

Započnimo.

Procesi nasuprot nitima: Koje su razlike?

Šta je proces?

Proces je svaka instanca programa koja zahteva izvršavanje.

To može biti bilo šta – Python skripta, veb pregledač poput Chrome-a ili aplikacija za video konferencije. Ako otvorite Task Manager na svom računaru i odete na Performance –> CPU, videćete procese i niti koji se trenutno izvršavaju na vašim CPU jezgrima.

Razumevanje procesa i niti

Interno, proces ima dodeljenu memoriju koja skladišti kod i podatke specifične za taj proces.

Proces se sastoji od jedne ili više niti. Nit je najmanji niz instrukcija koji operativni sistem može da izvrši i predstavlja tok izvršavanja.

Svaka nit ima svoj stek i registre, ali ne i namenski memorijski prostor. Sve niti povezane s istim procesom mogu da pristupaju podacima. Stoga, podaci i memorija se dele između svih niti unutar jednog procesa.

U CPU-u sa N jezgara, N procesa može da se izvršava paralelno istovremeno. Međutim, dve niti istog procesa nikada ne mogu da se izvršavaju paralelno, ali mogu da se izvršavaju istovremeno. U narednom odeljku ćemo detaljnije razmotriti koncept konkurentnosti u odnosu na paralelizma.

Na osnovu onoga što smo do sada naučili, sumirajmo razlike između procesa i niti.

Karakteristika Proces Nit
Memorija Dodeljena memorija Deljena memorija
Način izvršavanja Paralelno, konkurentno Konkurentno; ali ne paralelno
Upravljanje izvršenjem Operativni sistem CPython interpreter

Višenitnost u Pythonu

U Pythonu, Globalno Zaključavanje Interpretera (GIL) osigurava da samo jedna nit može da stekne zaključavanje i izvršava se u bilo kom trenutku. Sve niti moraju da steknu ovo zaključavanje da bi se izvršavale. Ovo garantuje da samo jedna nit može biti u stanju izvršavanja u bilo kom datom momentu i sprečava istovremenu višenitnost.

Na primer, zamislimo dve niti, t1 i t2, unutar istog procesa. Pošto niti dele iste podatke, kada t1 čita određenu vrednost x, t2 može da modifikuje tu istu vrednost x. Ovo može dovesti do blokiranja i neželjenih rezultata. Međutim, samo jedna od niti može da stekne zaključavanje i izvrši se u datom trenutku. Stoga, GIL takođe obezbeđuje bezbednost niti.

Dakle, kako ostvariti višenitne mogućnosti u Pythonu? Da bismo ovo razumeli, razmotrimo koncepte konkurentnosti i paralelizma.

Konkurentnost naspram Paralelizma: Pregled

Razmotrimo CPU sa više od jednog jezgra. Na slici ispod, CPU ima četiri jezgra. To znači da u svakom trenutku možemo imati četiri različite operacije koje se izvršavaju paralelno.

Ako postoje četiri procesa, svaki od njih može da se izvršava nezavisno i istovremeno na svakom od četiri jezgra. Pretpostavimo da svaki proces ima dve niti.

Da bismo razumeli kako funkcionise višenitnost, pređimo sa višejezgarnog na jednojezgrenu procesorsku arhitekturu. Kao što je ranije pomenuto, samo jedna nit može biti aktivna u određenoj instanci izvršavanja; ali jezgro procesora može prelaziti između niti.

Na primer, I/O vezane niti često čekaju I/O operacije: čitanje korisničkog unosa, čitanje iz baze podataka i operacije sa datotekama. Tokom ovog perioda čekanja, nit može da oslobodi zaključavanje kako bi se druga nit mogla pokrenuti. Vreme čekanja može biti i jednostavna operacija kao što je pauza u trajanju od n sekundi.

Ukratko: tokom operacija čekanja, nit oslobađa zaključavanje, omogućavajući jezgru procesora da pređe na drugu nit. Prethodna nit nastavlja sa izvršavanjem nakon što se završi period čekanja. Ovaj proces, gde se jezgro procesora istovremeno prebacuje između niti, omogućava višenitnost. ✅

Ako želite da implementirate paralelizma na nivou procesa u vašoj aplikaciji, razmotrite korišćenje multiprocesiranja.

Python Threading Modul: Prvi Koraci

Python dolazi sa modulom za obradu niti koji možete da uvezete u svoju Python skriptu.

import threading

Da biste kreirali objekat niti u Pythonu, možete da koristite konstruktor niti: threading.Thread(…). Ovo je generička sintaksa koja je dovoljna za većinu implementacija niti:

threading.Thread(target=...,args=...)

gde je:

  • target argument ključne reči koji označava Python pozivivu (callable) funkciju
  • args skup argumenata koje cilj uzima.

Potreban vam je Python 3.x da biste mogli pokretati primere koda u ovom uputstvu. Preuzmite kod i pratite ga.

Kako Definisati i Pokrenuti Niti u Pythonu

Hajde da definišemo nit koja pokreće ciljnu funkciju.

Ciljna funkcija je some_func.

import threading
import time

def some_func():
    print("Pokreće se some_func...")
    time.sleep(2)
    print("Završeno pokretanje some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

Analizirajmo šta radi gornji isečak koda:

  • Uvozi module threading i time.
  • Funkcija some_func ima deskriptivne print() izjave i uključuje operaciju spavanja u trajanju od dve sekunde: time.sleep(n) uzrokuje da funkcija „spava“ n sekundi.
  • Zatim definišemo nit thread_1 sa ciljem postavljenim na some_func. threading.Thread(target=…) kreira objekat niti.
  • Napomena: Navedite ime funkcije, a ne poziv funkcije; koristite some_func, a ne some_func().
  • Kreiranje objekta niti ne pokreće nit; potrebno je pozvati metodu start() na objektu niti.
  • Da bismo dobili broj aktivnih niti, koristimo funkciju active_count().

Python skripta radi na glavnoj niti, a mi kreiramo drugu nit (thread1) za pokretanje funkcije some_func, tako da je broj aktivnih niti dva, kao što se vidi u izlazu:

# Izlaz
Pokreće se some_func...
2
Završeno pokretanje some_func.

Ako pažljivije pogledamo izlaz, videćemo da se nakon pokretanja thread1, prva print izjava izvršava. Ali, tokom operacije mirovanja, procesor se prebacuje na glavnu nit i štampa broj aktivnih niti – bez čekanja da se thread1 završi sa izvršavanjem.

Čekanje da se niti završe sa izvršavanjem

Ako želite da se thread1 završi sa izvršavanjem, možete da pozovete metodu join() na njoj nakon pokretanja niti. To će sačekati da thread1 završi izvršavanje bez prelaska na glavnu nit.

import threading
import time

def some_func():
    print("Pokreće se some_func...")
    time.sleep(2)
    print("Završeno pokretanje some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

Sada je thread1 završio sa izvršavanjem pre nego što odštampamo aktivan broj niti. Dakle, samo glavna nit je pokrenuta, što znači da je broj aktivnih niti jedan. ✅

# Izlaz
Pokreće se some_func...
Završeno pokretanje some_func.
1

Kako pokrenuti više niti u Pythonu

Dalje, kreirajmo dve niti za pokretanje dve različite funkcije.

Ovde je count_down funkcija koja uzima broj kao argument i odbrojava od tog broja do nule.

def count_down(n):
    for i in range(n,-1,-1):
        print(i)

Definišemo count_up, još jednu Python funkciju koja broji od nule do datog broja.

def count_up(n):
    for i in range(n+1):
        print(i)

📑 Kada koristite funkciju range() sa opsegom sintakse (start, stop, step), krajnja tačka zaustavljanja je podrazumevano isključena.

– Za odbrojavanje od određenog broja do nule, možete koristiti negativnu vrednost koraka od -1 i postaviti vrednost zaustavljanja na -1 tako da je nula uključena.

– Slično tome, da biste brojali do n, morate postaviti vrednost zaustavljanja na n+1. Pošto su podrazumevane vrednosti za početak i korak 0 i 1, možete koristiti range(n+1) da biste dobili niz od 0 do n.

Zatim definišemo dve niti, thread1 i thread2, za pokretanje funkcija count_down i count_up, respektivno. Dodajemo print izjave i operacije pauze za obe funkcije.

Kada kreirate objekte niti, obratite pažnju da argumente ciljne funkcije treba navesti kao tuple – parametar args. Pošto obe funkcije (count_down i count_up) uzimaju jedan argument, moraćete eksplicitno da umetnete zarez posle vrednosti. Ovo osigurava da se argument i dalje prosleđuje kao tuple, jer se naredni elementi tretiraju kao None.

import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Pokreće se thread1....")
        print(i)
        time.sleep(1)

def count_up(n):
    for i in range(n+1):
        print("Pokreće se thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

U izlazu:

  • Funkcija count_up radi na thread2 i broji do 5 počevši od 0.
  • Funkcija count_down radi na thread1 i odbrojava od 10 do 0.
# Izlaz
Pokreće se thread1....
10
Pokreće se thread2...
0
Pokreće se thread1....
9
Pokreće se thread2...
1
Pokreće se thread1....
8
Pokreće se thread2...
2
Pokreće se thread1....
7
Pokreće se thread2...
3
Pokreće se thread1....
6
Pokreće se thread2...
4
Pokreće se thread1....
5
Pokreće se thread2...
5
Pokreće se thread1....
4
Pokreće se thread1....
3
Pokreće se thread1....
2
Pokreće se thread1....
1
Pokreće se thread1....
0

Možete da vidite da se thread1 i thread2 izvršavaju naizmenično, jer obe uključuju operaciju čekanja (spavanje). Kada funkcija count_up završi sa brojanjem do 5, thread2 više nije aktivan. Zbog toga imamo izlaz koji odgovara samo thread1.

Sumiranje

U ovom uputstvu ste naučili kako da koristite ugrađeni Python modul za obradu niti za implementaciju višenitnosti. Evo rezimea ključnih tačaka:

  • Konstruktor niti se može koristiti za kreiranje objekta niti. Korišćenjem threading.Thread(target=, args=()) kreira se nit koja pokreće ciljnu funkciju sa argumentima navedenim u args.
  • Python program se izvršava na glavnoj niti, tako da su objekti niti koje kreirate dodatne niti. Možete da pozovete funkciju active_count() koja vraća broj aktivnih niti u bilo kom trenutku.
  • Možete da pokrenete nit pomoću metode start() na objektu niti i da sačekate da se završi izvršavanje pomoću metode join().

Možete da kodirate dodatne primere podešavanjem vremena čekanja, isprobavanjem drugih I/O operacija i još mnogo toga. Obavezno implementirajte višenitnost u vaše predstojeće Python projekte. Srećno sa kodiranjem!🎉