线程

  1. 线程的介绍

    在Python中,想要实现多任务除了使用进程,还可以使用线程來完成,线程是实现多任务的另外一种方式。

  2. 线程的概念

    线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。

  3. 线程的作用

    多线程可以完成多任务

线程实现多任务

# 导包
import threading
import time


def sing():
    for i in range(5):
        print('正在唱歌中....')
        time.sleep(0.1)  # 让进程手动失去CPU资源  让进程休眠0.1秒,今日阻塞态,失去cpu


def dance():
    for i in range(5):
        print('正在跳舞中.....')
        time.sleep(0.1)


if __name__ == '__main__':
    # 创建线程对象
    thread_sing = threading.Thread(target=sing)
    thread_dance = threading.Thread(target=dance)

    # 启动线程
    thread_sing.start()
    thread_dance.start()

线程传参

# 导包
import threading
import time


def sing(singer, song):
    for i in range(5):
        print(f'{singer}正在唱歌{song}中....')
        time.sleep(0.1)  # 让进程手动失去CPU资源  让进程休眠0.1秒,今日阻塞态,失去cpu


def dance(dancer, name):
    for i in range(5):
        print(f'{dancer}正在跳{name}舞')
        time.sleep(0.1)


if __name__ == '__main__':
    # 创建线程对象
    thread_sing = threading.Thread(target=sing, args=('张学友', '野猫之恋'))
    thread_dance = threading.Thread(target=dance, kwargs={'dancer': '梁咏琪', 'name': '胆小鬼'})

    # 启动线程
    thread_sing.start()
    thread_dance.start()

线程的执行是随机的

# 导包
import threading
import time


def func():
    time.sleep(1)
    print('当前线程为:', threading.current_thread().name)


if __name__ == '__main__':
    # 创建10个线程
    for i in range(10):
        sub = threading.Thread(target=func)
        sub.start()

主进程会等待子进程结束再结束

# 导包
import threading
import time


def func():
    for i in range(5):
        print('子线程代码结束')
        time.sleep(0.5)


if __name__ == '__main__':
    # 想让子线程随着主线程的结束而结束,可以将子线程设置为 daemon 线程
    sub = threading.Thread(target=func, daemon=True)
    # 第二种方法
    # sub.daemon = True
    # 第三种方法
    # sub.SetDaemon(True)
    sub.start()
    time.sleep(1)
    print('主线程代码结束')

线程共享全局变量问题

# 导包
import threading
import time

g_list = []


def add_data():
    for i in range(5):
        g_list.append(i)
        print('添加数据', i)
        time.sleep(0.1)
    # 代码出了 for 循环,代表数据添加完成
    print('add_data', g_list)


# 定义任务函数,读取全局变量的值
def read():
    print('read', g_list)


if __name__ == '__main__':
    # 想让子线程随着主线程的结束而结束,可以将子线程设置为 daemon 线程
    thread_add = threading.Thread(target=add_data)
    thread_read = threading.Thread(target=read)
    thread_add.start()
    thread_add.join()
    thread_read.start()


# 线程共享全局变量出现的问题

多线程

线程阻塞解决资源竞争问题

# 导包
import threading
import time

g_num = 0


def task():
    # 修改全局变量的值需要使用 global 声明
    global g_num
    for i in range(1000000):
        g_num += 1

    print(threading.current_thread().name, g_num)


if __name__ == '__main__':
    thread_1 = threading.Thread(target=task)
    thread_2 = threading.Thread(target=task)
    thread_1.start()
    # 解决方案:让线程一先执行结束后在执行
    thread_1.join()
    thread_2.start()

互斥锁

  1. 互斥锁的概念

    互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作。

    注意:

    • 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。
  2. 互斥锁的使用

    threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。

    ```python

    导包

    import threading import time

    加锁

    mutex = threading.Lock() g_num = 0

def task():

   # 修改全局变量的值需要使用 global 声明
   global g_num
   for i in range(1000000):
       # 上锁
       mutex.acquire()
       g_num += 1
       # 解锁
       mutex.release()



   print(threading.current_thread().name, g_num)

if name == 'main': thread_1 = threading.Thread(target=task) thread_2 = threading.Thread(target=task) thread_1.start()

   # 解决方案:让线程一先执行结束后在执行
   # thread_1.join()  # 线程等待
   # 第二种 互斥锁
   thread_2.start()



   # 导包

import threading import time

mutex = threading.Lock() g_num = 0

def task():

   # 修改全局变量的值需要使用 global 声明
   global g_num
   # 上锁
   mutex.acquire()
   for i in range(1000000):
       g_num += 1
   # 解锁
   mutex.release()



   print(threading.current_thread().name, g_num)

if name == 'main': thread_1 = threading.Thread(target=task) thread_2 = threading.Thread(target=task) thread_1.start()

   # 解决方案:让线程一先执行结束后在执行
   # thread_1.join()  # 线程等待
   # 第二种 互斥锁
   thread_2.start()

这两种锁都可以


3. 死锁

   死锁:一直等待对方释放锁的情景就是死锁

   会造成应用程序的停止响应,不能再处理其它任务了。

   ```python
   # 导包
   import threading
   import time

   mutex = threading.Lock()
   g_list = [1, 2, 3, 4, 5]


   def func(index):
       mutex.acquire()
       if index >= len(g_list):
           print(threading.current_thread().name, '下标越界')
           mutex.release()  # 在锁的时候释放锁
           return
       print(threading.current_thread().name, g_list[index])
       mutex.release()


   if __name__ == '__main__':
       for i in range(10):
           sub = threading.Thread(target=func, args=(i,))
           sub.start()

区别对比

  1. 进程之间不共享全局变量
  2. 线程之间共享全局变量,但是要注意资源竟争的问题,解决办法:互斥锁或者线程同步
  3. 创建进程的资源开销要比创建线程的资源开销要大
  4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
  5. 线程不能够独立执行,必须依存在进程中
  6. 多进程开发比单进程多线程开发稳定性要强

优缺点对比

  • 进程的优缺点:
    • 优点:可以用多核
    • 缺点:资源开销大
  • 线程优缺点:
    • 优点:资源开销小
    • 缺点:不能使用多核

GIL锁

GIL 全局解释器锁

GIL 保证同一时间,只有一个线程使用 CPU。

一个进程有一个 GIL锁。

GIL 不是 Python 的特性,只是 cPython 解释器的概念。历史遗留问题。

  1. GIL锁什么时候释放?

    • 在当前线程执行超时后会自动释放
    • 在当前线程执行阻塞操作时会自动释放(input, io/输入输出)
    • 当前执行完成时
  2. G儿L的弊端

    • GIL对计算密集型的程序会产生影响。因为计算密集型的程序,需要占用系统资源。
    • GIL的存在,相当于始终在进行单线程运算,这样自然就慢了。
    • IO密集型影响不大的原因在于,IO,input/output,这两个词就表明程序的瓶颈在于输入输出所耗费的时间,线程大部分时间在等待,所以它们是多个一起等(多线程)还是单个等(单线程)无所谓的。
  3. 解决方案:

    要提升多线程执行效率,解决方案:

    • 更换解释器
    • 改为多进程替换多线程
    • 子线程使用C语言实现(绕过GIL锁)
  4. 必须要知道的是:

    • CPU 密集型不太适合多线程
    • I/O 密集型适合多线程 (Gil锁会释放)爬虫程序

results matching ""

    No results matching ""