python 线程

一、线程背景 (详情参考:https://www.cnblogs.com/clschao/articles/9684694.html)

  1、进程

  之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

  进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。

  2、为什么要有线程

   进程的不足:

(1)进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

(2)进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

  3、注意:(1)进程是资源分配的最小单位,线程是CPU调度的最小单位.

      (2)每一个进程中至少有一个线程。 
      (3)进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是                                    cpu上的执行单位。
 
二、进程与线程的关系
 

 

  线程与进程的区别可以归纳为以下4点:
  1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。(就类似进程中的锁的作用)
  3)调度和切换:线程上下文切换比进程上下文切换要快得多。
  4)在多线程操作系统中(现在咱们用的系统基本都是多线程的操作系统),进程不是一个可执行的实体,真正去执行程序的不是进程,是线程,你可以理解进程就是一个线程的容器。
 

  在这里我们简单总结一下:

    进程是最小的内存分配单位

    线程是操作系统调度的最小党委

    线程被CPU执行了

    进程内至少含有一个线程

    进程中可以开启多个线程 

      开启一个线程所需要的时间要远小于开启一个进程

      多个线程内部有自己的数据栈,数据不共享

      全局变量在多个线程之间是共享的

  
 
三、Threading 模块
  multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍( 官方链接
  1、线程的创建的两种方法
from threading import Thread

def fun(n):
    print(n)

if __name__ == '__main__':
    t=Thread(target=fun,args=(123,))
    t.start()

    print("主线程结束")
方式一:Thread (target=...)

 

class MyThread(Thread):
    def __init__(self,n):
        super(MyThread, self).__init__()
        self.n=n
    def run(self):
        print(self.n)
        print("xxxxxxxxxxxxx")

if __name__ == '__main__':
    t=MyThread(456)
    t.start()
    print("父线程jieshu")
方式二:class MyThread()

  

  2、线程 t.join() :等待子线程执行完后才会继续执行主线程的代码

import time
from threading import Thread

def func():
    time.sleep(1)
    print('我是子线程')

if __name__ == '__main__':

    t = Thread(target=func,)
    t.start()

    print('开始等待子线程了')
    t.join()  #阻塞,等待子线程执行完毕,才继续执行
    print('主线程结束')
线程join

  

  3、线程的其他方法

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
import time
from threading import Thread
from threading import current_thread
import threading


def func():
    time.sleep(3)

    # current_thread().ident
    print('我是子线程,名字是',current_thread().getName())
    print('我是子线程,id是',current_thread().ident)

if __name__ == '__main__':

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

        # print(t.isAlive())
    print(threading.enumerate())
    print(threading.activeCount())

    print('主线程结束')
其他方法使用实例

  

  4、进程与线程效率对比

import time
from threading import Thread
from multiprocessing import Process

def func():
    # time.sleep(3)
    print('xxxx')

if __name__ == '__main__':

    t_list = []
    t_s_t = time.time() #线程执行起始时间
    for i in range(100):
        t = Thread(target=func,)
        t_list.append(t)
        t.start()
    [tt.join() for tt in t_list]
    t_e_t = time.time() #线程执行结束时间
    t_dif_t = t_e_t - t_s_t #线程执行所需时间

    p_list = []
    p_s_t = time.time() #进程执行起始时间
    for i in range(100):
        p = Process(target=func,)
        p_list.append(p)
        p.start()
    [pp.join() for pp in p_list]
    p_e_t = time.time() #进程执行结束时间
    p_dif_t = p_e_t - p_s_t #进程执行所需时间
    print('多线程的时间>>>',t_dif_t)
    print('多进程的时间>>>',p_dif_t)

    print('主线程结束')
多进程与多线程效率对比

  

  5、数据共享信息安全问题验证

  结果:不可靠。会造成数据混乱。   

import time
from threading import Thread

num = 100

def func():
    # time.sleep(0.01)
    global num
    num -= 1

if __name__ == '__main__':

    # for i in range()

    t = Thread(target=func,)
    t.start()
    t.join()
    print('主线程的num',num)
修改全局变量的方式验证
import time
from threading import Thread

num = 100
def func():
    # time.sleep(3)
    global num
    tep = num
    time.sleep(0.001)
    tep = tep - 1
    num = tep
    # num -= 1

if __name__ == '__main__':
    t_list = []
    for i in range(100):
        t = Thread(target=func,)
        t_list.append(t)
        t.start()
    [tt.join() for tt in t_list]
    # t.join()
    print('主线程的num',num)
加长版验证

 

  6、验证子线程和主线程在同一个进程(os.getpid())
 
import time
from threading import Thread
import os

def func():
    time.sleep(1)

    print('我是子线程,我的pid是',os.getpid())

if __name__ == '__main__':

    t = Thread(target=func,)
    t.start()

    print('开始等待子线程了')
    print('主线程的pid',os.getpid())
    t.join()
    print('主线程结束')
同属一个进程验证

  

  7、加锁解决数据不安全问题 (牺牲了效率,保证了数据安全)

import time
from threading import Thread,Lock

num = 100
def func(tl):
    # time.sleep(3)
    print('xxxxx')
    time.sleep(1)
    global num
    tl.acquire() #上锁
    tep = num
    time.sleep(0.001)
    tep = tep - 1
    num = tep
    tl.release() #释放锁
    # num -= 1

if __name__ == '__main__':
    tl = Lock()
    t_list = []
    for i in range(10):
        t = Thread(target=func,args=(tl,))
        t_list.append(t)
        t.start()
    [tt.join() for tt in t_list]
    # t.join()
    print('主线程的num',num)
上锁解决数据不安全

  

  8、死锁现象

    现象描述:双方互相等待对方释放对方手里锁,然后拿到的那个锁

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

import time
from threading import Thread,Lock,RLock

def func1(lock_A,lock_B):
    lock_A.acquire()
    time.sleep(0.5)
    print('alex拿到了A锁')
    lock_B.acquire()
    print('alex拿到了B锁')
    lock_B.release()
    lock_A.release()

def func2(lock_A,lock_B):
    lock_B.acquire()
    print('taibai拿到了B锁')
    lock_A.acquire()
    print('taibai 拿到了A锁')
    lock_A.release()
    lock_B.release()

if __name__ == '__main__':
    lock_A = Lock()
    lock_B = Lock()
    t1 = Thread(target=func1,args=(lock_A,lock_B))
    t2 = Thread(target=func2,args=(lock_A,lock_B))
    t1.start()
    t2.start()
死锁现象

 

   

     死锁现象: 引入递归锁RLock(lock_A=lock_B=RLock())

    递归锁RLock自带计数器,遇到acquire计数器+1;遇到release,计数器-1,递归锁被一个对象夺走后就不能被其他对象获得,只有等释放为0时候才会开锁,让其他的进程抢夺。

 

import time
from threading import Thread,Lock,RLock

def func1(lock_A,lock_B):
    lock_A.acquire()
    time.sleep(0.5)
    print('alex拿到了B锁')
    lock_B.release()
    lock_A.release()

def func2(lock_A,lock_B):
    lock_B.acquire()
    print('taibai拿到了B锁')
    lock_A.acquire()
    print('taibai 拿到了A锁')
    lock_A.acquire()
    print('taibai 拿到了A锁')
    lock_A.release()
    lock_B.release()

if __name__ == '__main__':

    lock_A = lock_B = RLock()  #递归深度不限,只有当递归锁计数器为0,才会释放给其他线程用
    print(id(lock_A)) #具有相同内存地址
    print(id(lock_B)) #具有相同的内存地址

    t1 = Thread(target=func1,args=(lock_A,lock_B))
    t2 = Thread(target=func2,args=(lock_A,lock_B))
    t1.start()
    t2.start()
递归解决死锁问题

   典型问题:科学家吃面,只有一碗面,一把叉子

import time
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name)
    print('%s 吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['taibai','egon','wulaoban']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()
面条与叉子

 

   9、守护线程(守护进程必须在start之前)

  无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕,如果主xx时间够长,守护xx执行完毕后被销毁,如果主xx时间较短的话,守护xx会终并销毁,如果主。需要强调的是:运行完毕并非终止运行。

#1.对主进程来说,运行完毕指的是主进程代码运行完毕
#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

 

import time
from threading import Thread
from multiprocessing import Process

def func1():
    time.sleep(3)
    print('任务1结束')

def func2():
    time.sleep(2)
    print('任务2结束')

if __name__ == '__main__':

     t1 = Thread(target=func1,)
     t2 = Thread(target=func2,)
     # t1.daemon = True
    # t2.setDaemon(True)  #将t1设置为守护线程
     t1.setDaemon(True)  #将t1设置为守护线程
     t1.start()
     t2.start()
     print('主线程结束')

#结果显示
主线程结束
任务2

#结果分析:t1被设置为守护进程,他线程时间为3s,t2线程的时间是2s,
当t2线程执行完后,结束守护进程,主线程代码就会执行到print(”主线程“),
守护进程实例与解说
# from threading import Thread
# import time
#
#
# def sayhi(name):
#     time.sleep(2)
#     print('%s say hello' % name)
#
#
# if __name__ == '__main__':
#     t = Thread(target=sayhi, args=('taibai',))
#     t.setDaemon(True)  # 必须在t.start()之前设置
#     t.start()
#
#     print('主线程')
#     print(t.is_alive())
#     '''
#     主线程
#     True
#     '''

from threading import Thread
from multiprocessing import Process
import time


def func1():
    while True:
        print(666)
        time.sleep(0.5)


def func2():
    print('hello')
    time.sleep(3)


if __name__ == '__main__':
    # t = Thread(target=func1,)
    # t.daemon = True  #主线程结束,守护线程随之结束
    # # t.setDaemon(True) #两种方式,和上面设置守护线程是一样的
    # t.start()
    # t2 = Thread(target=func2,) #这个子线程要执行3秒,主线程的代码虽然执行完了,但是一直等着子线程的任务执行完毕,主线程才算完毕,因为通过结果你会发现我主线程虽然代码执行完毕了,\
    # 但是主线程的的守护线程t1还在执行,说明什么,说明我的主线程还没有完毕,只不过是代码执行完了,一直等着子线程t2执行完毕,我主线程的守护线程才停止,说明子线程执行完毕之后,我的主线程才执行完毕
    # t2.start()
    # print('主线程代码执行完啦!')
    p = Process(target=func1, )
    p.daemon = True
    p.start()

    p2 = Process(target=func2, )
    p2.start()
    time.sleep(1)  # 让主进程等1秒,为了能看到func1的打印效果
    print('主进程代码执行完啦!')  # 通过结果你会发现,如果主进程的代码运行完毕了,那么主进程就结束了,因为主进程的守护进程p随着主进程的代码结束而结束了,守护进程被回收了,这和线程是不一样的,主线程的代码完了并不代表主线程运行完毕了,需要等着所有其他的非守护的子线程执行完毕才算完毕
守护线程2实例

 

四、信号量

  

  同进程的一样

  Semaphore管理一个内置的计数器,
  每当调用acquire()时内置计数器-1;
  调用release() 时内置计数器+1;
  计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

  实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

import time
import random
from threading import Thread,Semaphore

def func1(i,s):
    s.acquire()
    # time.sleep(1)
    print('客官%s里边请~~'%i)
    time.sleep(random.randint(1, 3))
    s.release()


if __name__ == '__main__':
    s = Semaphore(4)
    for i in range(10):
        t = Thread(target=func1,args=(i,s))
        t.start()
Semaphore实例

 

五、事件(Event)

  事件的基本方法:

event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。

 

转载于:https://www.cnblogs.com/angle6-liu/p/10045634.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值