threadを運動会に例えて理解してみる

##1種目目 単純な20m競争

tanakakunとyoshidakunの競争
yoshidakunの方が早い想定

``` threading_test.py
import threading
import time
import datetime

## 時間表示をデコレータ関数でやりたかったが、mainスレッド処理になってしまうため断念
# def dips_time(func):
#     dt_now = datetime.datetime.now()
#     print(f"{dt_now.hour}:{dt_now.minute}:{dt_now.second}")
#     func()
#     dt_now = datetime.datetime.now()
#     print(f"{dt_now.hour}:{dt_now.minute}:{dt_now.second}")

def time_disp():
    dt_now = datetime.datetime.now()
    # 書式化した時間と生の状態2つをタプル型で返す
    return (f"{dt_now.hour}時:{dt_now.minute}分:{dt_now.second}秒", dt_now)

def runner(take_time=1):
    print(take_time)
    thread_name = threading.current_thread().getName()
    start_time,startt = time_disp()
    print(f"僕のスレッド名は {thread_name} です")
    print(f"{start_time}::{thread_name}行きます.")
    time.sleep(take_time)
    finish_time,finisht = time_disp()
    print(f"{finish_time}::{thread_name}フィニッシュ") 
    elapsed_time = finisht - startt
    print(f"経過時間::{elapsed_time}")


# def yoshidakun(take_time=1):
#     print(take_time)
#     thread_name = threading.current_thread().getName()
#     start_time,startt = time_disp()
#     print(f"僕のスレッド名は {thread_name} です")
#     print(f"{start_time}::{thread_name}行きます.")
#     time.sleep(take_time)
#     finish_time,finisht = time_disp()
#     print(f"{finish_time}::{thread_name}フィニッシュ") 
#     elapsed_time = finisht - startt
#     print(f"経過時間::{elapsed_time}")
    
if __name__ == '__main__':
    print('よーいどん')
    ## スレッドの引数はargsかkwargsで渡さないといけない。スレッド名はname=～で変更できる。
    t1 = threading.Thread(target=runner,kwargs={'take_time':5},name="tanakakun")
    t2 = threading.Thread(target=runner,kwargs={'take_time':3},name="yoshidakun")
    t1.start()
    t2.start()

    ## joinでスレッド処理が終了するのを待つ
    t1.join()
    t2.join()
    print("競争終了!!!!")

```

実行結果

```

よーいどん
5
3
僕のスレッド名は tanakakun です
僕のスレッド名は yoshidakun です
9時:35分:4秒::tanakakun行きます.
9時:35分:4秒::yoshidakun行きます.
9時:35分:7秒::yoshidakunフィニッシュ
経過時間::0:00:03.007000
9時:35分:9秒::tanakakunフィニッシュ
経過時間::0:00:05.006653
競争終了!!!!

```

## 2種目目　リレー大会

Aチーム：第1走者:tanakakun(5秒) 第2走者:yoshidakun(3秒)
Bチーム：第1走者:tiger(2秒) 　　第2走者:cat(9秒)

ルール : 第1走者がゴールするまで、第2走者はスタートできない。
このために、各スレッド毎にロックを利用する。

シナリオ：最初、Bチームがリードするが、Aチームが逆転する。


###　注意ポイント
- ロックはスレッドごとに別々のオブジェクトを作らなければならない。
- 同じロックを別々のスレッドに割り当てると、お互いに干渉してしまうので、
結局並列に処理がされなかった。
- この点に気づかず、最初ハマりました。teratailで質問し、ご回答をいただいて解決することができた。


``` threding_tes.py
import threading
import time
import datetime

lock = threading.Lock()
##　もう一つのロックを作成。これが大事！
lock2 = threading.Lock()

def time_disp():
    dt_now = datetime.datetime.now()
    # 書式化した時間と生の状態2つをタプル型で返す
    return (f"{dt_now.hour}時:{dt_now.minute}分:{dt_now.second}秒", dt_now)

def teamA(take_time=1,lock=lock):
    print(take_time)
    lock.acquire()
    thread_name = threading.current_thread().getName()
    start_time,startt = time_disp()
    print(f"僕のスレッド名は {thread_name} です")
    print(f"{start_time}::{thread_name}行きます.")
    time.sleep(take_time)
    finish_time,finisht = time_disp()
    print(f"{finish_time}::{thread_name}フィニッシュ") 
    elapsed_time = finisht - startt
    print(f"経過時間::{elapsed_time}")
    lock.release()

def teamB(take_time=1,lock=lock2):
    print(take_time)
    lock.acquire()
    thread_name = threading.current_thread().getName()
    start_time,startt = time_disp()
    print(f"僕のスレッド名は {thread_name} です")
    print(f"{start_time}::{thread_name}行きます.")
    time.sleep(take_time)
    finish_time,finisht = time_disp()
    print(f"{finish_time}::{thread_name}フィニッシュ") 
    elapsed_time = finisht - startt
    print(f"経過時間::{elapsed_time}")
    lock.release()
    
if __name__ == '__main__':
    print('よーいどん')
    ## スレッドの引数はargsかkwargsで渡さないといけない。スレッド名はname=～で変更できる。
    teamA1 = threading.Thread(target=teamA,kwargs={'take_time':5,'lock':lock},name="tanakakun")
    teamA2 = threading.Thread(target=teamA,kwargs={'take_time':3},name="yoshidakun")

    ## 別のロック(lock2)をこちらのスレッドに割り当てる
    teamB1 = threading.Thread(target=teamB,kwargs={'take_time':2,'lock':lock2},name="tiger")
    teamB2 = threading.Thread(target=teamB,kwargs={'take_time':9},name="cat")
    
    teamA1.start()
    teamB1.start()

    
    print("match")
    teamA2.start()
    teamB2.start()

    ## joinでスレッド処理が終了するのを待つ
   
    teamA1.join
    teamB1.join()
    teamA2.join()
    teamB2.join()
    print("競争終了!!!!")

```

実行結果

``` 
よーいどん
僕のスレッド名は tanakakun です  
18時:3分:49秒::tanakakun行きます.
僕のスレッド名は tiger です      
18時:3分:49秒::tiger行きます.    
18時:3分:51秒::tigerフィニッシュ
経過時間::0:00:02.012578   
僕のスレッド名は cat です  
18時:3分:51秒::cat行きます.
18時:3分:54秒::tanakakunフィニッシュ
経過時間::0:00:05.002115
僕のスレッド名は yoshidakun です  
18時:3分:54秒::yoshidakun行きます.
18時:3分:57秒::yoshidakunフィニッシュ
経過時間::0:00:03.001305
18時:4分:0秒::catフィニッシュ
経過時間::0:00:09.001530
競争終了!!!!

```
## 3種目目　玉入れ

Aチーム：tanakakun(2秒/投)※ 　yoshidakun(3秒/投)
Bチーム：tiger(4秒/投) 　cat(3秒/投)

※1玉投げるのにかかる時間

ルール
- AチームとBチームが一斉に玉を投げる。
- 玉入れ先は別々
- 制限時間　10秒
- 玉を多く入れたチームが勝ち

ポイント
- 同一スレッド内では変数共有するという特性を利用する。
- 同一スレッド内で10個達成した後に、もう一人が続けないようにdeamonをsetする。
- メインスレッド上で10秒カウントし、10秒経ったらglobal変数のフラグを立てて、
スレッドを終了させる。

``` tamaire.py
import threading
import time
import datetime

def time_disp():
    dt_now = datetime.datetime.now()
    # 書式化した時間と生の状態2つをタプル型で返す
    return (f"{dt_now.hour}時:{dt_now.minute}分:{dt_now.second}秒", dt_now)

def teamA(per_throw_time=1):
    global ball_countA
    ball_countA = 0
    for i in range(100):
        time.sleep(per_throw_time)
        global stop_threads
        if stop_threads:
            break
        nowtime,_=time_disp()
        print(nowtime)
        ball_countA += 1
        thread_name = threading.current_thread().getName()
        print(f"{thread_name}の{i+1}玉目 です。玉の合計は{ball_countA}")

def teamB(per_throw_time=1):
    global ball_countB
    ball_countB = 0
    for i in range(100):
        time.sleep(per_throw_time)
        global stop_threads
        if stop_threads:
            break
        nowtime,_=time_disp()
        print(nowtime)
        ball_countB += 1
        thread_name = threading.current_thread().getName()
        print(f"{thread_name}の{i+1}玉目 です。玉の合計は{ball_countB}")
        
    
if __name__ == '__main__':
    nowtime,_=time_disp()
    print(nowtime)
    print('よーいどん')

    teamA1 = threading.Thread(target=teamA,kwargs={'per_throw_time':2},name="tanakakun")
    teamA2 = threading.Thread(target=teamA,kwargs={'per_throw_time':3},name="yoshidakun")

    # teamA1.setDaemon(True)
    # teamA2.setDaemon(True)

    teamB1 = threading.Thread(target=teamB,kwargs={'per_throw_time':4},name="tiger")
    teamB2 = threading.Thread(target=teamB,kwargs={'per_throw_time':3},name="cat")
    
    # teamB1.setDaemon(True)
    # teamB2.setDaemon(True)

    stop_threads = False
    teamA1.start()
    teamB1.start()
    teamA2.start()
    teamB2.start()

    time.sleep(7)
    print("終了3秒前")
    nowtime,_=time_disp()
    print(nowtime)
    time.sleep(1)
    print("終了2秒前")
    nowtime,_=time_disp()
    print(nowtime)
    time.sleep(1)
    print("終了1秒前")
    nowtime,_=time_disp()
    print(nowtime)
    time.sleep(1)
    print("タイムアウト")
    nowtime,_=time_disp()
    print(nowtime)
    stop_threads = True

    ## joinでスレッド処理が終了するのを待つ

    teamA1.join()
    teamB1.join()
    teamA2.join()
    teamB2.join()

    print(f"Aチーム:{ball_countA}球")
    print(f"Bチーム:{ball_countB}球")

    if ball_countA>ball_countB:
        print("Aチームの勝ち")
    elif ball_countA<ball_countB:
        print("Bチームの勝ち")
    else:
        print("引き分け")


```

実行結果

```
22時:27分:37秒
よーいどん
22時:27分:39秒
tanakakunの1玉目 です。玉の合計は1
22時:27分:40秒
22時:27分:40秒
catの1玉目 です。玉の合計は1       
yoshidakunの1玉目 です。玉の合計は2
22時:27分:41秒
tigerの1玉目 です。玉の合計は2
22時:27分:41秒
tanakakunの2玉目 です。玉の合計は3
22時:27分:43秒
catの2玉目 です。玉の合計は3       
22時:27分:43秒
yoshidakunの2玉目 です。玉の合計は4
22時:27分:43秒
tanakakunの3玉目 です。玉の合計は5
終了3秒前
22時:27分:44秒
22時:27分:45秒
終了2秒前
tigerの2玉目 です。玉の合計は4
22時:27分:45秒
22時:27分:45秒
tanakakunの4玉目 です。玉の合計は6
22時:27分:46秒
22時:27分:46秒
yoshidakunの3玉目 です。玉の合計は7
catの3玉目 です。玉の合計は5
終了1秒前
22時:27分:46秒
22時:27分:47秒
tanakakunの5玉目 です。玉の合計は8
タイムアウト
22時:27分:47秒
Aチーム:8球
Bチーム:5球
Aチームの勝ち

```

eventやqueなどにも触れたったが、長くなったのここで一旦終了。
もっとこうすべきとかご意見お待ちしてます。