多线程编程

多线程编程简介:


多线程编程目的:

最大化利用系统资源,提高系统执行代码的效率。多线程(Multithread, MT)在本质上属于异步的。
不使用多线程的情况下可以使用多路复用,在系统开销中,多路复用小于多线程,但是多线程的效率高于多路复用
在这里插入图片描述

适用对象:

  1. 本质上属于异步
  2. 有多个并发的活动
  3. 每个活动的执行顺序具有不确定性

进程和线程之间的区别

  • 个人理解:进程之于系统相当于线程之于进程。线程有三部分包括:开始,执行顺序,结束。
  • 让步:其他线程运行时,他可以被挂起或者中断
  • 单核CPU中线程执行规划:
    1. 每个线程运行一小会,然后让步给其他进程(再次排队等待更多的CPU时间)。
    2. 联想到:在学习单片机的时候,有一个概念时地址总线,数据总线和控制总线,CPU每次从指令寄存器中取出指令,然后在根据地址通过数据总线取出相应的数据。CPU每次只负责处理,那么关于每条指令的调度将直接决定哪个进程被执行。

多线程编程实现方法:

  • thread模块(python3中更名为_thread)
  • threading模块(推荐)

两者之间的区别(目前个人了解):

  1. thread同步原语很少,threading模块中很多,同步原语为线程管理提供了很好的支持。

原语 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。primitive or atomic action 是由若干个机器指令构成的完成某种特定功能的一段程序,具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。

  1. threading模块会保持所有的子线程都退出的情况下,主线程才会退出,而thread模块则是在主线程结束后,所有的子线程不会得到清理。
  2. 守护线程的应用:守护线程是为了标记这个线程不重要,因为thread模块中主线程退出时,所有其他线程均结束,而在threading模块中,主线程必须等待其他线程结束才可以退出,当线程被设置为守护线程时,主线程退出时就不需要等待守护线程结束。(目前推荐:thread.daemon = True(不使用setDaemon的原因个人理解为:设计thread模块时,更加希望daemon作为一个特定的固有状态。)

我们只希望那些想访问线程更底层的专家使用thread模块

利用thread模块创建的等待函数

import _thread
from time import sleep, ctime

loops = [3, 2]


def loop(nloop, nsec, lock):
    print("loop {0} start at {1}.".format(nloop, ctime()))
    sleep(nsec)
    print("loop {0} end at {1}".format(nloop, ctime()))
    lock.release()


def main():
    print("program start at {}".format(ctime()))
    locks = []
    nloops = range(len(loops))

'''创建锁对象并放在一个locks列表中'''
    for i in nloops:
        lock = _thread.allocate_lock()
        lock.acquire()
        locks.append(lock)

'''创建nloops列表个名为nloops[i]的线程'''
    for i in nloops:
        _thread.start_new_thread(loop, (i, loops[i], locks[i]))

'''检查n各线程中锁对象是否为False'''
    for i in nloops:
    '''检查第一个线程的锁对象,
    线程未被释放时locks[0].locked()返回值一直为True,
    释放完成后检查第二个线程是否被释放
    '''
        while locks[i].locked():			
            pass
'''
#同理可以检测locks列表中的值是否全部为False
	for i in locks:
		while i.locked():
			pass
'''

    print('all Done at {0}'.format(ctime()))


if __name__ == '__main__':
    main()

输出结果:

Connected to pydev debugger (build 182.4505.26)
program start at Thu Nov 22 13:23:20 2018
loop 1 start at Thu Nov 22 13:23:20 2018.
loop 0 start at Thu Nov 22 13:23:20 2018.
loop 1 end at Thu Nov 22 13:23:22 2018
loop 0 end at Thu Nov 22 13:23:23 2018
all Done at Thu Nov 22 13:23:23 2018

小结:这个程序利用locks列表对各个线程进行跟踪,一边进行控制

利用threading模块创建多线程:

  • 主要方法
    1. 利用thread类创建一个thread实例,传给他一个函数
    2. 创建一个thread实例,传给它一个可调用的类实例
    3. 派生一个thread子类,并且创建一个子类的实例
  • 使用时如何选择:
    在需要一个更架符合对象的接口时,会选择第三个方法,否则选择第一个,至于第二个如何还需要了解。
  • 利用Threading模块创建等待示例:
    1.利用thread类创建一个thread实例,传给他一个函数
mport threading
from time import sleep, ctime

loops = [3, 2]


def loop(nloop, nsec):
    print("loop {0} start at {1}.".format(nloop, ctime()))
    sleep(nsec)
    print("loop {0} end at {1}".format(nloop, ctime()))


def main():
    print("program start at {}".format(ctime()))
    thread = []
    nloops = range(len(loops))

    for i in nloops:
        thread.append(threading.Thread(target=loop,
                                       args=(nloops[i], loops[i],)))

    for i in thread:
        i.start()

    for i in thread:
        i.join()

    print('all Done at {0}'.format(ctime()))


if __name__ == '__main__':
    main()
  1. 创建一个thread实例,传给它一个可调用的类实例
import threading
from time import sleep, ctime

loops = [3, 2]


class ThreadFunc:
 def __init__(self, func, args, name=''):
     self.func = func
     self.name = name
     self.args = args

 def __call__(self):
     self.func(*self.args)


def loop(nloop, nsec):
 print("loop {0} start at {1}.".format(nloop, ctime()))
 sleep(nsec)
 print("loop {0} end at {1}".format(nloop, ctime()))


def main():
 print("program start at {}".format(ctime()))
 thread = []
 nloops = range(len(loops))

 for i in nloops:
     thread.append(threading.Thread(
                             target=ThreadFunc(loop,
                                             (i, loops[i]),
                                             loop.__name__))
                             )

 for i in thread:
     i.start()

 for i in thread:
     i.join()

 print('all Done at {0}'.format(ctime()))


if __name__ == '__main__':
 main()

个人理解调整后代码(不一定友好):

import threading
from time import sleep, ctime

loops = [3, 2]


class ThreadFunc:
 def __init__(self, args, name=''):
     self.name = name
     self.args = args

 def __call__(self):
     self.loop(*self.args)

 def loop(self, nloop, nsec):
     print("loop {0} start at {1}.".format(nloop, ctime()))
     sleep(nsec)
     print("loop {0} end at {1}".format(nloop, ctime()))


def main():
 print("program start at {}".format(ctime()))
 thread = []
 nloops = range(len(loops))

 for i in nloops:
     thread.append(threading.Thread(target=ThreadFunc((i, loops[i]),)))

 for i in thread:
     i.start()

 for i in thread:
     i.join()

 print('all Done at {0}'.format(ctime()))


if __name__ == '__main__':
 main()

流程解释:

Created with Raphaël 2.2.0 main for i in nloops nloops遍历完成? 启动线程 加入线程 结束 创建线程(参数) args=(i, loops[i]), name=' ' 调用类方法 call调用loop传入参数 args,此时args参数为元组,匹配loop的两个形参 自动调用__call__ yes no
  1. 派生一个thread子类,并且创建一个子类的实例
import threading
from time import sleep, ctime

loops = [3, 2]


class MyThread(threading.Thread):
    def __init__(self, func, args, name=''):
        threading.Thread.__init__(self)
        self.func = func
        self.name = name
        self.args = args

    def run(self):
        return self.func(*self.args)


def loop(nloop, nsec):
    print('Starting thread', nloop+1, 'at ', ctime(), end='\n')
    sleep(nsec)
    print('thread', nloop+1, 'Finish at ', ctime())


def main():
    print('main thread begin at ', ctime())
    nloops = range(len(loops))
    thread = []

    for i in nloops:
        thread.append(MyThread(loop, (i, loops[i]), loop.__name__))

    for i in thread:
        i.start()

    for i in thread:
        i.join()

    print('All Done at ', ctime())


if __name__ == '__main__':
    main()

为了让代码能够更加的通用,改写了Mythread类

import threading
from time import sleep, ctime


class MyThread(threading.Thread):
    def __init__(self, args, name=''):
        threading.Thread.__init__(self)		#此处换成super().__init__会报错,原因不像
        self.func = sleep
        self.name = name
        self.args = args

    def getresult(self):
        return self.res

    def run(self):
        print('Starting thread', self.name, 'at ', ctime(), end='\n')
        self.func(self.args[1])
        print('thread', self.name, 'Finish at ', ctime())


def main():
    print('main thread begin at ', ctime())
    loops = [3, 2]
    thread = []

    for i in range(len(loops)):
        thread.append(MyThread((i, loops[i]), i))

    for i in thread:
        i.start()

    for i in thread:
        i.join()

    print('All Done at ', ctime())


if __name__ == '__main__':
    main()
  • 针对改写程序中的问题
super().__init__(self)

不能替代

threading.Thread.__init__(self)
  • 小结
  1. 关于join()方法,join是在主线程必须等待子线程必须完成后才可以继续执行,如果主线程没有必要等待子线程完成,那么就没有必要使用join(),但是主线程执行完成时,如果子线程还没完成,那么threading中的主线程会等待子线程完成。
  2. 在第二个案例中我调整了代码结构,将loop直接写在了class里面,但是在调整代码的时候发现__call__的理解不够深入,经过了解后,发现适当类被当作函数被调用时,会自动调用call函数。
  3. 在重构函数时需要清晰的了解每个传入参数的意义,同时在定义函数时也应该明确知道各个参数表达的意义,所以个人建议在自己编写函数的时候在函数的开头协商注释,注释包含内容:函数的功能,函数需要的参数以及用途。
  4. 编写代码时应该尽量的简洁并且逻辑结构清晰

同步原语:

  • 在程序运行的时候,我们希望对某些代码块只被一个线程所执行,例如:数据库块,由于多线程同时进行,无法保证每次对数据的执行都是目标的数据。
  • 实现方法:
  1. 锁/互斥
import threading

lock = threading.Lock()
lock.acquire()
'''
操作代码
'''
lock.release
  • 个人理解为是给操作代码上了锁,然后需要获得锁才能执行锁内代码,否则就在该代码外被挂起,指导获得锁。
  1. 信号量
  • 个人理解:在资源池中,固定可开销资源的数量,当在一定的范围你可同时访问资源池内的资源时,使用BoundedSemaphore来控制可同时使用资源的线程数目,当资源数目少与默认时,会使线程挂起。
import threading
from time import sleep, ctime
import random
from atexit import register

lock = threading.Lock()
MAX = 5
candyTray = threading.BoundedSemaphore(MAX)


def refill():
    lock.acquire()
    print('Refilling candy...')
    try:
        candyTray.release()
    except ValueError:
        print('Full,skipping...')
    else:
        print('OK')
    lock.release()


def buy():
    lock.acquire()
    print('Buying candy...')
    if candyTray.acquire(False):
        print('OK')
    else:
        print('Empty,skipping...')
    lock.release()


def producer(loops):
    for i in range(loops):
        refill()
        sleep(random.randrange(0, 3, 1))


def consumer(loops):
    for i in range(loops):
        buy()
        sleep(random.randrange(0, 3, 1))


def main():
    print('Starting at:', ctime())
    nloops = (random.randrange(2, 6))
    print("The candy machine (full with {0} candy)".format(MAX))
    threading.Thread(target=consumer, args=(random.randrange(nloops, nloops+MAX+2),)).start()
    threading.Thread(target=producer, args=(nloops,)).start()


@register
def _atexit():
    print('Time at {0} all Done...'.format(ctime()))


if __name__ == '__main__':
    main()
  • 要求:给出各个时刻的信号量的精确值
'''目前水平做了一件很愚蠢的改写'''
import threading
from time import sleep, ctime
import random
from atexit import register

lock = threading.Lock()
MAX = 5


class MyBoundedSemaphore(threading.BoundedSemaphore):
   def __init__(self, value):
       threading.BoundedSemaphore.__init__(self, value)
       self._inventory = value

   def getinventory(self):
       return str(self._inventory)

   def release(self):
       with self._cond:
           if self._value >= self._initial_value:
               raise ValueError("Semaphore released too many times")
           self._value += 1
           self._inventory += 1
           self._cond.notify()

   def acquire(self, blocking=True, timeout=None):
       """Acquire a semaphore, decrementing the internal counter by one.

       When invoked without arguments: if the internal counter is larger than
       zero on entry, decrement it by one and return immediately. If it is zero
       on entry, block, waiting until some other thread has called release() to
       make it larger than zero. This is done with proper interlocking so that
       if multiple acquire() calls are blocked, release() will wake exactly one
       of them up. The implementation may pick one at random, so the order in
       which blocked threads are awakened should not be relied on. There is no
       return value in this case.

       When invoked with blocking set to true, do the same thing as when called
       without arguments, and return true.

       When invoked with blocking set to false, do not block. If a call without
       an argument would block, return false immediately; otherwise, do the
       same thing as when called without arguments, and return true.

       When invoked with a timeout other than None, it will block for at
       most timeout seconds.  If acquire does not complete successfully in
       that interval, return false.  Return true otherwise.

       """
       if not blocking and timeout is not None:
           raise ValueError("can't specify timeout for non-blocking acquire")
       rc = False
       endtime = None
       with self._cond:
           while self._value == 0:
               if not blocking:
                   break
               if timeout is not None:
                   if endtime is None:
                       endtime = _time() + timeout
                   else:
                       timeout = endtime - _time()
                       if timeout <= 0:
                           break
               self._cond.wait(timeout)
           else:
               self._value -= 1
               self._inventory -= 1
               rc = True
       return rc


candyTray = MyBoundedSemaphore(MAX)


def refill():
   lock.acquire()
   print('Refilling candy...')
   try:
       candyTray.release()
   except ValueError:
       print('Full,skipping...', 'inventory', candyTray.getinventory())
   else:
       print('inventory', candyTray.getinventory())
   lock.release()


def buy():
   lock.acquire()
   print('Buying candy...')
   if candyTray.acquire(False):
       print('inventory', candyTray.getinventory())
   else:
       print('Empty,skipping...', 'inventory', candyTray.getinventory())
   lock.release()


def producer(loops):
   for i in range(loops):
       refill()
       sleep(random.randrange(0, 3, 1))


def consumer(loops):
   for i in range(loops):
       buy()
       sleep(random.randrange(0, 3, 1))


def main():
   print('Starting at:', ctime())
   nloops = (random.randrange(2, 6))
   print("The candy machine (full with {0} candy)".format(MAX))
   threading.Thread(target=consumer, args=(random.randrange(nloops, nloops+MAX+2),)).start()
   threading.Thread(target=producer, args=(nloops,)).start()


@register
def _atexit():
   print('Time at {0} all Done...'.format(ctime()))


if __name__ == '__main__':
   main()
  • 愚蠢的地方:
    1. 重写了两个类,虽然时复制粘贴,在其中增加了计数的标准
    2. 增加了获取信号量的功能
  • 希望改进方向:
    1. 在原函数不变的情况下使用装饰器,进行增加
    2. 后续了解深入继续修改
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值