Python マルチスレッド ------> これは素晴らしいので、ぜひ見てみてはいかがでしょうか

目次

マルチタスク

プログラム内でマルチタスクをシミュレートする

マルチタスクの理解

マルチタスク用のスレッド

スレッド数を表示する

サブスレッドの実行と作成を確認する

Thread クラスを継承してスレッドを作成する

マルチスレッド共有グローバル変数(スレッド間通信)

マルチスレッド引数 -args

共有グローバル可変リソース競争

ミューテックス

デッドロック

デッドロックを回避する

キュースレッド

____________________________________________________________

マルチタスク

車の運転中に両手と両足を使って運転するなど、物事を同時に行う場面が多くあります。
運転、例えば歌や踊りも同時に行われ、それらは全て最後に完成させることで道路上を走行し、
名前が示すように、多くのことを同時に行うことができ、これらのことはタスクです
通常のコードを示してみましょう。
class sing():
    for i in range(5):
        print("我爱python")
    print("我爱python---------运行结束")


def dence():
    for i in range(5):
        print("Pyhon很好玩")
    print("python很好玩---------运行结束")



if __name__ == '__main__':
    sing()
    dence()

結果:

 コードが上から下に順番に実行されることがわかりますが、コードの実行に遅延がないかどうかを考えたことはありますか? 遅延がある場合は、下方向に実行できるか、遅延したコードはコードはプッシュバックされます。もちろん、マルチスレッドがあります。以下にそれを紹介します。

マルチタスクについて理解する:

同時実行性と並列処理について聞いたことがある人もいるはずです。これら 2 つの言葉はスレッドでよく聞かれます。かわいい人は、コードが CPU 上で実行されることを知っています。CPU はシングルコアとマルチコアに分かれています。下の図を描いてみましょう 、

 これらすべてのプロセスを実行したい場合、シングルコア CPU では不可能ですが、QQ が CPU 内に短時間滞在すると、WeChat の直後に起動して実行され、しばらく滞在してから終了します。そして、次のプロセスが入ってきて、外に出ると、これらのプロセスが同時に実行されていると誤って考えることができますが、実際には存在しません。これが並行性、並行性です。偽のマルチタスクです。 CPU が現在実行中のタスクより小さい

マルチコア CPU が実現可能であることがわかります。これは並列処理、並列処理です。実際のマルチタスク CPU は、現在実行中のタスクよりも大きいです。

マルチタスク用のスレッド

コードのデモは次のとおりです。

from threading import Thread
def sing():
    #子线程
    for i in range(5):
        print("我爱python")
    print("我爱python---------运行结束")


def dence():
    #子线程
    for i in range(5):
        print("Pyhon很好玩")
    print("python很好玩---------运行结束")


"""这是一个主线程"""
if __name__ == '__main__':
    for i in range(2):
        # 创建线程对象(这还不算是创建线程完成)
        sin=Thread(target=sing)
        # 启动(这里才算完成线程创建)
        sin.start()

結果:

わからなくても大丈夫、一つずつ説明していきます。

まず、以前に紹介したスレッド化モジュールをダウンロードする必要があるため、ここではあまり紹介しません。

インポートスレッド

スレッド オブジェクトを作成します (スレッドは完全には作成されていません)。

サブスレッドの実行と作成を確認します。

Thread を呼び出しても、スレッドは作成されません。
Threadで作成したインスタンスオブジェクトのstartメソッドを呼び出すとスレッドが作成され、起動されます。
このスレッドの実行を開始してください。
パラメータの対象: 受信関数名
パラメータ artgs: artgs=(3,5 などのタプルを渡します。簡単なポイントはパラメータを関数に渡すことです。ここでの関数はサブスレッドです。関数 (サブスレッド) がタスクだと思います。
start(): スレッドを開始します
join(): メイン プログラムは、子スレッドの実行後にのみ実行できます。
この図では、通常コードを作成するのと同じように、単一のスレッドを作成しました。マルチスレッドを見てみましょう。
from threading import Thread
import time
def sing():
    #子线程
    for i in range(5):
        print("我爱python")
    print("我爱python---------运行结束")
    time.sleep(10)


def dence():
    #子线程
    for i in range(5):
        print("Pyhon很好玩")
    print("python很好玩---------运行结束")


"""这是一个主线程"""
if __name__ == '__main__':
    a=time.time()
    for i in range(2):
        # 创建线程对象(这还不算是创建线程完成)
        sin=Thread(target=sing)
        # 启动(这里才算完成线程创建)
        sin.start()
    b=time.time()
    print(b-a)

結果:

 出力時間はメインプログラムの実行時間であることがわかります。サブスレッドの実行時間を見てみましょう

from threading import Thread
import threading
import time
def sing():
    #子线程
    for i in range(100):
        print("我爱python")
    print("我爱python---------运行结束")
    time.sleep(2)



def dence():
    #子线程
    for i in range(100):
        print("Pyhon很好玩")
    print("python很好玩---------运行结束")


"""这是一个主线程"""
if __name__ == '__main__':
    a=time.time()
    lis=[]
    for i in range(1):
        # 创建线程对象(这还不算是创建线程完成)
        t=threading.Thread(target=sing)
        # 启动(这里才算完成线程创建)
        t.start()
        lis.append(t)
    for i in lis:
        i.join()
    b = time.time()
    print(b-a)
    print("主程序运行到结尾")

結果:

 join() は、メインプログラムの実行が開始される前に、子スレッドの実行が完了するのを待ちます。

Web ページ (通常バージョンとマルチスレッド バージョン) を一緒にクロールして、実行時間を確認してみましょう。

import requests
import threading
from lxml import etree
import time


def prase_url(url,header):
    response=requests.get(url, headers=header)
    return response


def parse_data(html):
    e_html=etree.HTML(html)
    new_html=e_html.xpath('//div[@id="htmlContent"]//text()')
    # print("".join(new_html).strip())
    h1=e_html.xpath('//div[@class="chapter-detail"]/h1/text()')[0]
    print(h1)
    return h1,"".join(new_html).strip()


def save_data(data):
    with open("./小说/{}.txt".format(data[0]),"w",encoding="utf-8")as f:
        f.write(data[1])

def main(urls):
    """主要的业务逻辑"""
    # url
    for url in urls:
        header={
            "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
        }
        # 发送请求获取响应
        response=prase_url(url,header)
        html=response.text
        # print(html)

        # 数据的提取
        data=parse_data(html)
        # 保存
        save_data(data)


if __name__ == '__main__':
    a=time.time()
    lis=[]
    urls=[]
    for i in range(56, 93):
        url = "http://www.quannovel.com/read/620/2467{}.html".format(i)
        urls.append(url)
    # for i in range(2):
    #     t1=threading.Thread(target=main,args=(urls,))
    #     t1.start()
    #     lis.append(t1)
    # for t in lis:
    #     t.join()

    # 单线程

    main(urls)
    b=time.time()
    print(b-a)
    print("主线程运行结束,等待子线程运行结束")

 最初のマルチスレッド:

import requests
import threading
from lxml import etree
import time


def prase_url(url,header):
    response=requests.get(url, headers=header)
    return response


def parse_data(html):
    e_html=etree.HTML(html)
    new_html=e_html.xpath('//div[@id="htmlContent"]//text()')
    # print("".join(new_html).strip())
    h1=e_html.xpath('//div[@class="chapter-detail"]/h1/text()')[0]
    print(h1)
    return h1,"".join(new_html).strip()


def save_data(data):
    with open("./小说/{}.txt".format(data[0]),"w",encoding="utf-8")as f:
        f.write(data[1])

def main(urls):
    """主要的业务逻辑"""
    # url
    for url in urls:
        header={
            "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
        }
        # 发送请求获取响应
        response=prase_url(url,header)
        html=response.text
        # print(html)

        # 数据的提取
        data=parse_data(html)
        # 保存
        save_data(data)


if __name__ == '__main__':
    a=time.time()
    lis=[]
    urls=[]
    for i in range(56, 93):
        url = "http://www.quannovel.com/read/620/2467{}.html".format(i)
        urls.append(url)
    for i in range(2):
        t1=threading.Thread(target=main,args=(urls,))
        t1.start()
        lis.append(t1)
    for t in lis:
        t.join()

    # 单线程

    # main(urls)
    b=time.time()
    print(b-a)
    print("主线程运行结束,等待子线程运行结束")
import requests
import threading
from lxml import etree
import time


def prase_url(url,header):
    response=requests.get(url, headers=header)
    return response


def parse_data(html):
    e_html=etree.HTML(html)
    new_html=e_html.xpath('//div[@id="htmlContent"]//text()')
    # print("".join(new_html).strip())
    h1=e_html.xpath('//div[@class="chapter-detail"]/h1/text()')[0]
    print(h1)
    return h1,"".join(new_html).strip()


def save_data(data):
    with open("./小说/{}.txt".format(data[0]),"w",encoding="utf-8")as f:
        f.write(data[1])

def main(i):
    """主要的业务逻辑"""
    # url
    url="http://www.quannovel.com/read/620/2467{}.html".format(i)
    header={
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
    }
    # 发送请求获取响应
    response=prase_url(url,header)
    html=response.text
    # print(html)

    # 数据的提取
    data=parse_data(html)
    # 保存
    save_data(data)


if __name__ == '__main__':
    a=time.time()
    lis=[]
    for i in range(56,93):
        t1=threading.Thread(target=main,args=(i,))
        t1.start()
        lis.append(t1)
    for t in lis:
        t.join()

    # 单线程
    # for i in range(56,93):
    #     main(i)
    b=time.time()
    print(b-a)
    print("主线程运行结束,等待子线程运行结束")

結果

可愛い子たちはこれを見れば何が起こっているのか分かるでしょう、スレッドに時間がかかりすぎます、スレッドに問題があるため、一つずつ分析しましょう、

最初のマルチスレッド: 複数のスレッドが一緒に書き込みを行うため、スレッドの実行時間が CPU によって決定されるため、リソースの競合が発生します。

かわいい子たちが実行しているときに、5 つのスレッドの実行結果が正しくないことがわかります。その理由は何ですか? つまり、各スレッドは最初から最後まで実行されます。スレッドが作成されるたびに、パラメーターは介入すると、私たちが商品を購入するたびに、売り切れたら販売者が商品を補ってくれるようです, t1=threading.Thread(target=main, args =(urls,)) がそのような原則であるか、削除を設計するか、すべてを一度に実行します。

注意してみると、主にリソースの競合により、データの保存が乱れていることがわかります。

 上図に示すように、最初のマルチスレッドはこのような状況になります。これを防ぎたい場合は、ここで各スレッドが実行されるようにロックを追加し、次のスレッドが書き込みできないようにする必要があります。後でロックを使用します。

2 番目のマルチスレッド:多数のスレッドを作成したことがわかりますが、その結果、作成した各スレッドは 1 つの Web サイトのみをクロールすることになり、コードの実行に目に見えない負担が追加されます。大事なことですが、私たちが作成するスレッドはすべて適切なものである必要があります。

Thread クラスを継承してスレッドを作成する

import requests
import threading
from lxml import etree
import time
import queue


# 这个类用于爬取数据
class My_Thread(threading.Thread):
    def __init__(self,urls,header,datas):
        super().__init__()
        self.urls=urls
        self.header=header
        self.datas=datas
        # print(self.urls.qsize())

    def prase_url(self,url):

        response=requests.get(url, headers=self.header)
        return response

    def parse_data(self,html):
        e_html=etree.HTML(html)
        new_html=e_html.xpath('//div[@id="htmlContent"]//text()')
        # print("".join(new_html).strip())
        h1=e_html.xpath('//div[@class="chapter-detail"]/h1/text()')[0]
        # print(h1)
        print("获取中")
        return (h1,"".join(new_html).strip())

    def run(self):
        """主要的业务逻辑"""
        while not self.urls.empty():
            # url
            a=self.urls.get()
            # 发送请求获取响应
            response=self.prase_url(a)
            html=response.text
            # 数据的提取
            data=self.parse_data(html)
            self.datas.put(data)
            # print(self.datas.qsize())


# 这个类用于保存文件
class Save_data(threading.Thread):
    def __init__(self,datas):
        super().__init__()
        self.datas=datas
        print(1)


    def run(self):
        while not self.datas.empty():
            a=self.datas.get()
            print("保存中")
            with open("./小说/{}.txt".format(a[0]), "w", encoding="utf-8")as f:
                f.write(a[1])


def main():

    # url
    urls = queue.Queue()
    datas=queue.Queue()
    for i in range(56, 93):
        url = "http://www.quannovel.com/read/620/2467{}.html".format(i)
        urls.put(url)
    # print(urls.qsize())
    header={
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
    }
   # 创建多线程
    lis=[]
    for i in range(5):
        my_thead=My_Thread(urls,header,datas)
        my_thead.start()
        lis.append(my_thead)
    for i in lis:
        i.join()
    # print(datas.get())
    for i in range(5):
        sa_da=Save_data(datas)
        sa_da.start()






if __name__ == '__main__':
    a=time.time()
    main()
    b=time.time()
    print(b-a)
    print("主线程运行结束,等待子线程运行结束")

結果:

 マルチスレッド操作を見ると、一度にクロールできることがわかります。次に、コードを分析してみましょう。

1. Threa クラスを作成するときは、親クラス threading.Thread を継承する必要があります。

2. その中には run(self) というメソッドがあります!!! これは書き換えることができますが、名前は変更できません。実行する必要があるコードを書くことは、マルチを学習していないときの main() と同等です。ねじ切り

3. 内部の mian() 関数は主にスレッドの作成に使用されます。

4.

lis の i の場合: 
    i.join()

これを記述するには、クロールされたすべてのデータがクロールダウンするまで待機し、後でマルチスレッドをファイルに保存するときにデータが保存されないようにする必要があります。

5.queue.Queue(): キューを作成します。

マルチスレッド共有グローバル変数(スレッド間通信)

簡単に言うと、データ送信を完了するための媒体としてグローバル変数を使用します。

次に、グローバル変数を変更するには、global を追加する必要があるかという問題に遭遇します。

実際に:
関数内でグローバル変数を変更する場合、グローバルを追加するかどうかは、グローバル変数が
数量のポインタが変更されました。ポインタが変更された場合は、グローバルを使用する必要があり、ポインタのみが変更されます。
指向性空間内のデータは、現時点ではグローバルを使用する必要はありませんが、
かわいい子たちを示す簡単なコードを書いてみましょう。
import threading
# 修改全局变量是否要加global(根据修改值是否发生地址的改变,地址改变就要加global)
num=0
#写
def task1(nu,n):
    global num
    num+=nu
    print("task1=",num)
    print("n=%d"%n)

#读
def task2():
    print("task2=",num)


def main():

    # 创建子线程
    t1=threading.Thread(target=task1,args=(3,4))
    t2=threading.Thread(target=task2)
    # 启动子线程(这里才是是完全创建子线程)
    t1.start()
    t2.start()
    print("main.....",num)


if __name__ == '__main__':
    main()

結果:

 関数にグローバルを追加するかどうかは、値を変更することでアドレスが変更されるかどうかによって異なります。

ミューテックス

リソース競合を解決するために使用されます、いわゆるリソース競合とは、複数のスレッドが同じ方向に動作することを意味します。

別の簡単なコードを次に示します。

ロックなし:

import threading
import time

"""两个同时写入,不加锁"""
num=0

def task1():
    global num
    for i in range(100000000):
        num+=1
    print("task1.......%d"%num)


def task2():
    global num
    for i in range(100000000):
        num+=1
    print("task2.......%d"%num)


def main():
    # 创建子线程
    t1=threading.Thread(target=task1)
    t2=threading.Thread(target=task2)
    # 启动
    t1.start()
    t2.start()
    print("main....%d"%num)


if __name__ == '__main__':
    main()

結果:

 

コード:

RLock() を使用して複数のロックを作成する

import threading
import time
"""加锁"""

num=0
# 创建一个锁
# mutex=threading.Lock()
mutex=threading.RLock()

def task1():
    global num
    # 锁定(保证数据能正常存储)
    mutex.acquire()
    mutex.acquire()
    for i in range(100000000):
        num+=1
    mutex.release()
    mutex.release()
    # 解锁(使下一个线程能使用)
    print("task1.......%d"%num)


def task2():
    global num
    # 锁定(保证数据能正常存储)
    mutex.acquire()
    mutex.acquire()
    for i in range(100000000):
        num+=1
    mutex.release()
    mutex.release()
    # 解锁(使下一个线程能使用)
    print("task2.......%d"%num)

def main():
    # 创建子线程
    t1=threading.Thread(target=task1)
    t2=threading.Thread(target=task2)
    # 启动
    t1.start()
    t2.start()
    print("main....%d"%num)


if __name__ == '__main__':
    main()

結果:

 Lock を使用してロックを作成する

import threading
import time
""加锁"""

num=0
# 创建一个锁
mutex=threading.Lock()
# mutex=threading.RLock()

def task1():
    global num
    # 锁定(保证数据能正常存储)
    # mutex.acquire()
    mutex.acquire()
    for i in range(100000000):
        num+=1
    mutex.release()
    # mutex.release()
    # 解锁(使下一个线程能使用)
    print("task1.......%d"%num)


def task2():
    global num
    # 锁定(保证数据能正常存储)
    mutex.acquire()
    # mutex.acquire()
    for i in range(100000000):
        num+=1
    mutex.release()
    # mutex.release()
    # 解锁(使下一个线程能使用)
    print("task2.......%d"%num)

def main():
    # 创建子线程
    t1=threading.Thread(target=task1)
    t2=threading.Thread(target=task2)
    # 启动
    t1.start()
    t2.start()
    print("main....%d"%num)


if __name__ == '__main__':
    main()

結果:

 コード:

import threading
import time

mutex=threading.Lock()
def task1():
    global num
    with mutex:
        for i in range(100000000):
            num+=1
    print("task1.......%d"%num)


def task2():
    global num
    with mutex:
        for i in range(100000000):
            num+=1
    print("task2.......%d"%num)





def main():
    # 创建子线程
    t1=threading.Thread(target=task1)
    t2=threading.Thread(target=task2)
    # 启动
    t1.start()
    t2.start()
    print("main....%d"%num)


if __name__ == '__main__':
    main()

結果;

 threading.Lock()、作成できるロックは 1 つだけ、ロックを解除できるのは 1 つだけです

 threading.RLock()、複数のロックの作成とロック解除のみ可能

取得() ロック
解放() ロック解除
ミューテックスあり: 自動的にロックおよびロック解除されます。オープンの場合と同じ効果があります。

キュースレッド

スレッドでは、いくつかのグローバル変数へのアクセスとロックが通常のプロセスです。何か数字を入れたい場合
データがキューに保存されている場合、Python にはキュー モジュールと呼ばれるスレッドセーフ モジュールが組み込まれています。
ピース。Python のキュー モジュールは、FIFO などの同期でスレッドセーフなキュー クラスを提供します。
(先入れ先出し) キュー Queue、LIFO (後入れ先出し) キュー LifoQueue。これらのキューは、
ロック プリミティブ (アトミック操作として理解できます。つまり、実行しないか、すべて実行します) は、複数行で使用できます。
プロセスで直接使用されます。キューを使用して、スレッド間の同期を実現できます。
簡単に言えば、このキューを作成した人はロックが面倒だと思ったので、簡単にするためにこれを作成しました。
コードは以下のように表示されます。
import queue
import threading

# 创建队列
# a=queue.Queue(5)
# for i in range(5):
#     a.put(i) #存入元素
#     print(a.full())
# print(a)
# for i in range(5):
#     # print(a.get())
#     print(a.get_nowait())
#     print(a.empty())




# 创建队列
q=queue.Queue()

num = 0
q.put(num)#把num的值存入


def task1():

    for i in range(10000000):
        num=q.get() # 创建一个名为num的局部变量
        num+=1
        q.put(num)
    # return q # 反不返回没事



def task2():

    for i in range(1000000):
        num = q.get()  # 创建一个名为num的局部变量
        num += 1
        q.put(num)
    # return q


def main():
    # 创建子线程
    t1=threading.Thread(target=task1)
    t2=threading.Thread(target=task2)
    # 启动
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    print("main....%d"%num)


if __name__ == '__main__':
    main()
Initialize Queue ( maxsize ) : 先入れ先出しキューを作成します。
qsize () : キューのサイズを返します。
empty () : キューが空かどうかを判断します。空の場合は True が返されます
full () : キューがいっぱいかどうかを判断します。完全な場合は True が返されます
get () : キューから最後のデータを取得します。
put () : データをキューに入れます。

要約する

一般に、スレッドの目的は、時間の利用を大幅に改善し、コンピュータ操作の効率を向上させ、スレッドがないとクロールが多すぎるため、実行が非常に遅くなるのを防ぐことです。

おすすめ

転載: blog.csdn.net/m0_69984273/article/details/130976961