JoinableQueue队列,线程,线程于进程的关系,使用线程,线程的特点,守护线程,线程的互斥锁,死锁问题,递归锁,信号量

  1.JoinableQueue队列

JoinableQueue([maxsize]):这就像是一个Queue对象,但是队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

案例:

from multiprocessing import JoinableQueue
# join是等待某个任务完成 able 可以  Queue 队列
# 翻译过来被join的队列
q = JoinableQueue()

q.put('123')
q.put('456')
print('取走一个了%s'% q.get())
q.task_done()
# 可以当作是普通的Queue的队列来使用


# task_done方法,是告诉队列这个数据已经被处理完了
# 需要注意的是,此处的处理完的并不是任务全部处理完成,
# 而指的是当前取出的数据已经处理完成。
# 所以每一个get 需要对应一个 task_done
# task_done 不能超过get的次数,否则会报多次掉用的错误
print('又取走了一个%s'% q.get())
q.task_done()


# 如果队列中的值已经被取空了,再次使用get取值会卡死
# 因为他在一直在等待有人往容器里存值
# print('又取走了一个%s'% q.get())
# q.task_done()


# join方法,等待队列中的数据被处理完毕,
# 一般在使用的时候,join 与 task_done 的次数 == put 的调用次数
q.join()
print('结束')

  2. 线程:

什么是线程:

  线程是操作系统最小的运算调度单位,被包含在进程中,一个线程就是一个固定的执行流程

  3.线程于进程的关系 ***** (重点)

  线程是不能单独存在的,必须存在进程中,进程是一个资源单位,其包含了运行程序所需的所有资源

  线程才是真正的执行单位,没有线程,进程中的资源就无法被利用起来,所以一个进程至少包含一个线程,这个线程就称之为主线程

  当我们启动一个程序时,操作系统就会自己为这个程序创建一个主线程,而由程序后期开启的线程,就称之为子线程

4.使用线程:

在使用之前,首先要想一个问题,为什么需要使用线程?

  其实目的只有一个,那就是提高效率

如果把进程比喻成车间,那么线程就是车间的流水线。

我们提高效率,当然可以再造一个新车间,那需要把原材料运过去 ,这个过程是非常耗时的

所以通常情况是创建新的流水线 而不是车间   

怎么使用?

  使用方法和多进程是一模一样的,只是使用了不同的模块而已

模块名:threading

类名:Thread

不一样的是,开启线程是不需要放在__main__下面,因为他们共用同一个进程的数据,而进程是将所有的数据全都重新拷贝了一份

案例:

第一种,直接实例化使用

from threading import Thread

# 第一种。直接调用Thread模块

def task():
    print('子进程开启')
    print('子进程结束')


t1 = Thread(target=task)

t1.start()
print('主线程结束')

第二种继承Thread,然后覆盖run方法

# 第二种,继承Thread类,覆盖run方法
class MyThread(Thread):
    def run(self):
        print('子线程开启')
        print('子线程结束')


t = MyThread()
t.start()
print('主线程结束')

5.线程的特点

  1.创建开销小,不用像进程需要导入数据

  2.同一个进程中的多个线程数据共享

  3.多个线程之间是平等的,没有父子关系。

案例1:同一进程中的线程数据共享

# 证明同一进程中的线程共享数据

from threading import Thread

# 定义一个全局变量
a = 10
def task():
    global a
    print('这是子线程')
    a = 20

t1 = Thread(target=task)

t1.start()
t1.join()

print(a)  # 此处的值已经被修改了,加入是在子进程中,是不会被修改的
          # 因为进程之间的数据并不互通

案例二:程序线程的开销小于进程

import os, time
from threading import Thread
from multiprocessing import Process

def task():
    pass

if __name__ == '__main__':
    st_time = time.time()

    ts = []
    for i in range(100):
        # t = Thread(target=task)  # 线程输出结果0.01894855499267578 (不固定,因设备而定)
        t = Process(target=task)   # 进程输出结果9.261330604553223 (不固定,因设备而定)
        t.start()
        ts.append(t)
    for t in ts:
        t.join()

    # 使用当前时间减去记录的时间会得到开启线程所用的时间
    print(time.time() - st_time)

    print('主线程结束')

6.守护线程

一个线程可以设置为另一个线程的守护线程

特点: 被守护线程结束后守护线程也随之结束

案例:

from threading import Thread
import time

def task1():
    print('第一子进程开始')
    time.sleep(2)
    print('第一子进程结束')

def task2():
    print('第二子进程开始')
    time.sleep(1)
    print('第二子进程结束')

t = Thread(target=task1)
t.daemon = Thread
t.start()

t1 = Thread(target=task2)
t1.start()

print('主线程结束')
# 上述案例是将 t 线程作为守护线程
# 运行顺序是:
    # 第一子进程开始
    # 第二子进程开始
    # 主线程结束
    # 第二子进程结束

# 主线程代码执行完毕后。不会立即结束,会等待其他子线程结束,主线程会等等待非守护线程 (即t2)
# 主线程会等待所有非守护线程结束后结束

# 守护线程会等到所有非守护线程结束后结束,如果守护线程已经完成任务,守护线程会立即结束
 主线程代码执行完毕后。不会立即结束,会等待其他子线程结束,主线程会等等待非守护线程 (即t2)
 主线程会等待所有非守护线程结束后结束
 守护线程会等到所有非守护线程结束后结束,如果守护线程已经完成任务,守护线程会立即结束

 

7.线程的互斥锁

共享资源就意味着竞争

线程中也存在安全问题,

多线程可以并发执行,一旦并发了并且访问了同一个资源就会有问题

解决方案:还是互斥锁

案例:

from threading import Thread,enumerate,Lock
import time

number = 10
lock = Lock()

def task():
    global number
    # 使用互斥锁来锁定资源
    lock.acquire()
    a = number
    time.sleep(0.1)
    number = a - 1
    # 使用完毕后释放
    lock.release()

for i in range(10):
    t = Thread(target=task)
    t.start()

for t in enumerate()[1:]:
    t.join()
print(number)

# 只要是并发执行,在访问同一资源的时候就会出现问题,所以与进程一样
# 就需要锁来控制资源

 

8.死锁问题

from threading import Lock, current_thread, Thread

'''
死锁问题
当程序出现了不止一把锁,分别被不同的线程持有,如果有一个资源要想使用必须同时具备两把锁
这时候程序就会进入无限卡死状态,这就称之为死锁
例如:
    要吃饭,必须具备盘子和筷子,但是一个人拿着盘子等筷子。另一个人拿着筷子等盘子
如何避免死锁问题:
    锁不要有多个,一个足够
    如果真的发生了死锁的问题,必须迫使一方先交出锁
    
'''
import time

lock1 = Lock()

lock2 = Lock()


def eat1():
    lock1.acquire()
    print('%s抢到了盘子' % current_thread().name)
    time.sleep(0.5)
    lock2.acquire()
    print('%s抢到了筷子' % current_thread().name)
    print('%s开动了' % current_thread().name)
    lock2.release()
    print('%s放下筷子' % current_thread().name)

    lock1.release()
    print('%s放下盘子' % current_thread().name)



def eat2():
    lock2.acquire()
    print('%s抢到了筷子' % current_thread().name)
    time.sleep(0.5)
    lock1.acquire()
    print('%s抢到了盘子' % current_thread().name)
    print('%s开动了' % current_thread().name)
    lock1.release()
    print('%s放下盘子' % current_thread().name)

    lock2.release()
    print('%s放下筷子' % current_thread().name)

t1 = Thread(target=eat1)

t2 = Thread(target=eat2)

t1.start()
t2.start()

 

9.递归锁

Rlock 称之为递归锁或者可重入锁

Rlock不是用来解决死锁问题的

与Lock唯一的区别: Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次

如果一个线程已经执行过acquire 其他线程将无法执行acquire

from threading import RLock,Lock,Thread
r = RLock()

def task():
    r.acquire()
    print('子进程开始')
    r.release()


# 主线程锁了一次
r.acquire()
r.acquire()


# 有几次acquire,就需要对应几次release
r.release()
r.release()

t1 = Thread(target=task)
t1.start()

10.信号量

'''
信号量
Lock Rlock
可以限制被锁定的代码,同时可以被多少线程并发访问
Lock 锁住一个马桶,同时只能有一个人
Semaphore 锁住的是公共厕所,同时可以来一堆人
用途:仅用于控制并发访问,并不能防止并发修改造成的问题
'''
from threading import Semaphore,Thread

import time

# 设置每次访问的线程数量,它自带锁
s = Semaphore(5)
def task():
    # 使用锁把代码锁住,让其他进程暂时无法访问资源
    s.acquire()
    print('子run')
    time.sleep(2)
    print('子over')
    s.release()
for i in range(10):
    t = Thread(target=task)
    t.start()

 总结:

多线程

线程是CPU的最小执行单位, 线程本质是一堆代码构成的执行流程

线程被包含在进程中,

进程是一个资源单位,其中存储着该程序运行所需所有资源, 可以比喻为一个车间

线程就是车间中国一条流水线,上面放的是制作产品的具体方法(就是的代码)

 

一个进程至少有一个线程,操作系统在运行一个程序时 会在进程中自动开启一条线程

一个进程中可以有多个线程

同一个进程中的线程共享进程内的数据

创建线程速度非常快 开销小

线程之间没有父子关系 都是平等的

 

 

使用方式和进程一致

学习多线程 多进程 都是为了提高效率 ,通过并发执行多个任务

实现并发的方式,多线程和多进程

  

原文地址:https://www.cnblogs.com/liguodeboke/p/10975869.html