# cPy - demonstrating pre-emptive threading
# Provided by Anthony Vogelaar, Perth Scotland
# rev 0.10  2024-06-03  initial release

import  os;             os.system('clear')
import  time

# ============================ Low level ===========================

import  _thread

def demo1():

    def task(ch, delay, count):
        for i in range(count):
            print(ch, end='', flush=True)
            time.sleep(delay)

    _thread.start_new_thread(task, ('1', 1,  5))
    _thread.start_new_thread(task, ('2', 1, 10))
    # for i in range(15):
    for i in range(5):                                    # threads stop when main terminates
        print('*', end='', flush=True)
        time.sleep(1)
    print('\nmain and threads done')

# ============================ Three tasks ==========================

import  threading

def demo2():

    def task(ch, delay, count):
        for i in range(count):
            print(ch, end='', flush=True)
            time.sleep(delay)


    t1 = threading.Thread(target=task, args=('1', 1,  5))   # Create object of Thread class
    t2 = threading.Thread(target=task, args=('2', 1, 10))
    t1.start()                                              # start the thread
    t2.start()
    # for i in range(15):
    for i in range(5):
        print('*', end='', flush=True)

def demo3():

    def task(ch, delay, count):
        for i in range(count):
            print(ch, end='', flush=True)
            time.sleep(delay)


    t1 = threading.Thread(target=task, args=('1', 1, 10))
    t2 = threading.Thread(target=task, args=('2', 1, 20))
    t1.start()
    t2.start()
    for i in range(3):
        print('*', end='', flush=True)
        time.sleep(1)
    print('\nwait 1')
    t1.join()
    print('\nt1 done')
    for i in range(3):
        print('*', end='', flush=True)
        time.sleep(1)
    print('\nwait 2')
    t2.join()
    print('\nt2 done')
    print('done')

# ========================== Critical section =========================
    
def demo4():
  
    x = [0]

    def increment(): 
        nonlocal x 
        y = x[0]
        time.sleep(0.0001)
        y = y + 1
        x[0] = y
  
    def task(): 
        for _ in range(1000): increment() 
  
    x[0] = 0
    t1 = threading.Thread(target=task); t2 = threading.Thread(target=task) 
    t1.start();                         t2.start()          # start tasks
    t1.join();                          t2.join()           # wait until tasks ready
    print(f'Result = {x[0]}    (expect 2000)') 

# ====================== Critical section & lock ======================
    
def demo5():
  
    x = [0]

    def increment(): 
        nonlocal x 
        y = x[0]
        time.sleep(0.001)
        y = y + 1
        x[0] = y
  
    def task(lock):
        lock.acquire() 
        for _ in range(1000): increment() 
        lock.release()
  
    x[0] = 0
    lock = threading.Lock() 
    t1 = threading.Thread(target=task, args=(lock,)); t2 = threading.Thread(target=task, args=(lock,)) 
    t1.start();                                       t2.start()          # start tasks
    t1.join();                                        t2.join()           # wait until tasks ready
    print(f'Result = {x[0]}    (expect 2000)') 

# ========================= pre-emptive time ==========================
    
def demo6():

    def task():
        longs = []
        t0 = time.time()
        t1 = time.time()
        n  = 0
        for i in range(500000):
            t2 = time.time()
            dt = t2 - t1
            t1 = t2
            if dt > 0.001: longs.append(dt)
            else         : n += 1
        t3 = time.time()
        res = [f'{x*1000:.3f} ' for x in longs]
        print(f't_total: {(t3-t0)*1000:.1f} ms,  {res}')
        
        
    _thread.start_new_thread(task, ())
    _thread.start_new_thread(task, ())
    time.sleep(1)
    print('\ndone')

# ============================== Main =================================

# demo1()
# demo2()
# demo3()
# demo4()
# demo5()
demo6()
          
# =============================== End =================================
