לדלג לתוכן

6.9 תהליכונים, א סיכרוניות הרצאה

תכנות רב תהליכונים - Multithreading Programming

  • תכנות רב תהליכונים, מאפשר לנו לכתוב תוכנה שיכולה להריץ כמה דברים במקביל.
  • תהליכון או באנגלית thread זה יחידת עיבוד, תהליכון יכול להריץ קוד, כך שתכנות רב תהליכונים זה כאשר לתוכנה שלנו יש מספר תהליכונים - מספר thread-ים

מודול threading

  • המודול מאפשר לנו לכתוב תוכנות עם מספר תהליכונים
  • מודול מובנה
    import threading
    import time
    
    def print_numbers():
        for i in range(5):
            time.sleep(1)
            print(f"Thread 1: {i}")
    
    def print_letters():
        for letter in 'ABCDE':
            time.sleep(1)
            print(f"Thread 2: {letter}")
    
    # Create two threads
    thread1 = threading.Thread(target=print_numbers)
    thread2 = threading.Thread(target=print_letters)
    
    # Start the threads
    thread1.start()
    thread2.start()
    
    # Wait for both threads to finish
    thread1.join()
    thread2.join()
    
    print("Multithreading Example Completed")
    
  • אפשר לראות שיצרנו 2 תהליכונים שונים, שני התהליכונים ירוצו באותו הזמן - שני הפונקציות print_numbers ו - print_letters ירוצו באותו הזמן

ביטחות עם תהליכונים

  • בתכנות עם מספר תהליכונים, יכול לקרות מצב שבו שני תהליכונים מנסים לשנות את אותו משתנה בו זמנית וזה יכול לגרום לדברים לא צפויים. למשל ראו את הדוגמה הבאה:
    import threading
    
    shared_variable = 0
    
    def increment_shared_variable():
        global shared_variable
        for _ in range(1000000):
            shared_variable += 1
    
    def decrement_shared_variable():
        global shared_variable
        for _ in range(1000000):
            shared_variable -= 1
    
    # Create two threads
    thread1 = threading.Thread(target=increment_shared_variable)
    thread2 = threading.Thread(target=decrement_shared_variable)
    
    # Start the threads
    thread1.start()
    thread2.start()
    
    # Wait for both threads to finish
    thread1.join()
    thread2.join()
    
    print("Shared Variable:", shared_variable)
    
  • יש פה שני תהליכונים שמנסים לשנות את shared_variable בו זמנית, והמחשב שלנו לא טוב בלבצע 2 פעולות שקשורת לאותו משתנה בו זמנית. ועלול לקרות מצב שבו המחשב יריץ רק את אחת הפעולות ולא את שניהן, זה יכול לגרום לבאגים, באגים קשים לפתירה, לא משהו שנרצה להתקל בו.
  • אז מה עושים? אנחנו יכולים לנעול את הפעולה שאנחנו עושים עם מנעול, ורק כאשר התהליכון מסיים את הפעולה הוא פותח את המנעול, ורק אז התהליכון השני יוכל לבצע פעולה בעצמו - ראו בקוד הבא:
    import threading
    
    shared_variable = 0
    lock = threading.Lock()  # Creating a new lock
    
    def increment_shared_variable():
        global shared_variable
        for _ in range(1000000):
            with lock:  # Only when the lock is free, the thread can access the `shared_variable`
                shared_variable += 1
    
    def decrement_shared_variable():
        global shared_variable
        for _ in range(1000000):
            with lock:  # Only when the lock is free, the thread can access the `shared_variable`
                shared_variable -= 1
    
    # Create two threads
    thread1 = threading.Thread(target=increment_shared_variable)
    thread2 = threading.Thread(target=decrement_shared_variable)
    
    # Start the threads
    thread1.start()
    thread2.start()
    
    # Wait for both threads to finish
    thread1.join()
    thread2.join()
    
    print("Shared Variable:", shared_variable)
    
  • המנעול הזה נקרא mutex, והוא מאפשר למחשב להריץ איזשהו קטע קוד בבטחה - לא יפריעו לו באמצע עד שלא סיים את הפעולה.
  • קיימים עוד סוגים של מנעולים כמו mutex, כמו semaphore - מוזמנים לקרוא עליהם.
  • כשאנחנו כותבים קוד עם מספר thread-ים אנחנו נשתמש במנעולים.

תכנות אסיכרוני

  • בדיוק כמו תכנות עם מספר תהליכונים, תכנות אסיכרוני מאפשר לנו להריץ דברים בו זמנית, ההבדל בא לידי ביטוי באיך שהמחשב עושה את זה מאחורי הקלעים, מוזמנים לחפש בעצמכם באינטרנט את ההבדלים.

  • מודול asyncio

  • מאפשר לנו לכתוב קוד אסיכרוני בפייתון
  • מודול מובנה.
    import asyncio
    
    async def print_numbers():
        for i in range(5):
            await asyncio.sleep(1)
            print(f"Async Task 1: {i}")
    
    async def print_letters():
        for letter in 'ABCDE':
            await asyncio.sleep(1)
            print(f"Async Task 2: {letter}")
    
    # Create an event loop
    async def main():
        task1 = asyncio.create_task(print_numbers)
        task2 = asyncio.create_task(print_letters)
    
        await asyncio.gather(task1, task2)
    
    # Run the event loop
    asyncio.run(main)
    
  • פונקציות שמסומנות עם "async" הן פונקציות אסכרוניות, הן פונקציות שיכולות לרוץ בצורה אסיכרונית - זה אומר שברגע שנקרא להן הן ירוצו והקוד ימשיך לרוץ - ממש כמו thread-ים.
  • כשאנחנו קוראים לפונקציה אסיכרונית עם "await", אנחנו בעצם מחכים לפונקציה שתסיים לרוץ ונעצור את הקוד עד שהיא חוזרת למורות שהיא אסכרונית.

מודול aiohttp

  • ספרייה שמאפשרת לנו לשלוח בקשות אינטרנט (בקשות לאתרים) בצורה אסכרונית
  • הריצו - pip install aiohttp
    import aiohttp
    import asyncio
    
    async def fetch_url(url):
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                return await response.text()
    
    async def main():
        urls = ['https://www.example.com', 'https://www.example.org', 'https://www.example.net']
    
        tasks = [fetch_url(url) for url in urls]
        responses = await asyncio.gather(*tasks)
    
        for url, response in zip(urls, responses):
            print(f"URL: {url}, Response Length: {len(response)}")
    
    asyncio.run(main())
    
  • הספרייה נותנת לכם את האפשרות לכתוב קוד שניגש לאתרים בצורה מאוד מהירה, תחשבו שאתם יכולים לשלוח בו זמנית למספר אתרים בקשה, ולחכות לתשובה מכולם ביחד במקום לשלוח בקשה אחד אחד.