上次说了很多Linux下进程相关知识,这边不再复述,下面来说说Python的并发编程,如有错误欢迎提出~

如果遇到听不懂的可以看上一次的文章:https://www.cnblogs.com/dotnetcrazy/p/9363810.html

官方文档:https://docs.python.org/3/library/concurrency.html

在线预览:http://github.lesschina.com/python/base/concurrency/2.并发编程-进程篇.html

1.进程篇

官方文档:https://docs.python.org/3/library/multiprocessing.html

Code:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/PythonProcess

1.1.进程(Process)

Python的进程创建非常方便,看个案例:(这种方法通用,fork只适用于Linux系)

  1. import os
  2. # 注意一下,导入的是Process不是process(Class是大写开头)
  3. from multiprocessing import Process
  4.  
  5. def test(name):
  6. print("[子进程-%s]PID:%d,PPID:%d" % (name, os.getpid(), os.getppid()))
  7.  
  8. def main():
  9. print("[父进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
  10. p = Process(target=test, args=("萌萌哒", )) # 单个元素的元组表达别忘了(x,)
  11. p.start()
  12. p.join() # 父进程回收子进程资源(内部调用了wait系列方法)
  13.  
  14. if __name__ == '__main__':
  15. main()

运行结果:

  1. [父进程]PID25729PPID23434
  2. [子进程-萌萌哒]PID25730PPID25729

创建子进程时,传入一个执行函数和参数,用start()方法来启动进程即可

join()方法是父进程回收子进程的封装(主要是回收僵尸子进程(点我)

其他参数可以参考源码 or 文档,贴一下源码的init方法:

def __init__(self,group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)

扩展:name:为当前进程实例的别名

  1. p.is_alive() 判断进程实例p是否还在执行
  2. p.terminate() 终止进程(发SIGTERM信号)

上面的案例如果用OOP来实现就是这样:(如果不指定方法,默认调Run方法)

  1. import os
  2. from multiprocessing import Process
  3.  
  4. class My_Process(Process):
  5. # 重写了Proce类的Init方法
  6. def __init__(self, name):
  7. self.__name = name
  8. Process.__init__(self) # 调用父类方法
  9.  
  10. # 重写了Process类的run()方法
  11. def run(self):
  12. print("[子进程-%s]PID:%d,PPID:%d" % (self.__name, os.getpid(),
  13. os.getppid()))
  14.  
  15. def main():
  16. print("[父进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
  17. p = My_Process("萌萌哒") # 如果不指定方法,默认调Run方法
  18. p.start()
  19. p.join() # 父进程回收子进程资源(内部调用了wait系列方法)
  20.  
  21. if __name__ == '__main__':
  22. main()

PS:multiprocessing.Process自行处理僵死进程,不用像os.fork那样自己建立信号处理程序、安装信号处理程序


1.1.源码拓展

现在说说里面的一些门道(只想用的可以忽略)

新版本的封装可能多层,这时候可以看看Python3.3.X系列(这个算是Python3早期版本了,很多代码都暴露出来,比较明了直观)

multiprocessing.process.py

  1. # 3.4.x开始,Process有了一个BaseProcess
  2. # https://github.com/python/cpython/blob/3.7/Lib/multiprocessing/process.py
  3. # https://github.com/lotapp/cpython3/tree/master/Lib/multiprocessing/process.py
  4. def join(self, timeout=None):
  5. '''一直等到子进程over'''
  6. self._check_closed()
  7. # 断言(False就触发异常,提示就是后面的内容
  8. # 开发中用的比较多,部署的时候可以python3 -O xxx 去除所以断言
  9. assert self._parent_pid == os.getpid(), "只能 join 一个子进程"
  10. assert self._popen is not None, "只能加入一个已启动的进程"
  11. res = self._popen.wait(timeout) # 本质就是用了我们之前讲的wait系列
  12. if res is not None:
  13. _children.discard(self) # 销毁子进程

multiprocessing.popen_fork.py

  1. # 3.4.x开始,在popen_fork文件中(以前是multiprocessing.forking.py)
  2. # https://github.com/python/cpython/blob/3.7/Lib/multiprocessing/popen_fork.py
  3. # https://github.com/lotapp/cpython3/tree/master/Lib/multiprocessing/popen_fork.py
  4. def wait(self, timeout=None):
  5. if self.returncode is None:
  6. # 设置超时的一系列处理
  7. if timeout is not None:
  8. from multiprocessing.connection import wait
  9. if not wait([self.sentinel], timeout):
  10. return None
  11. # 核心操作
  12. return self.poll(os.WNOHANG if timeout == 0.0 else 0)
  13. return self.returncode
  14.  
  15. # 回顾一下上次说的:os.WNOHANG - 如果没有子进程退出,则不阻塞waitpid()调用
  16. def poll(self, flag=os.WNOHANG):
  17. if self.returncode is None:
  18. try:
  19. # 他的内部调用了waitpid
  20. pid, sts = os.waitpid(self.pid, flag)
  21. except OSError as e:
  22. # 子进程尚未创建
  23. # e.errno == errno.ECHILD == 10
  24. return None
  25. if pid == self.pid:
  26. if os.WIFSIGNALED(sts):
  27. self.returncode = -os.WTERMSIG(sts)
  28. else:
  29. assert os.WIFEXITED(sts), "Status is {:n}".format(sts)
  30. self.returncode = os.WEXITSTATUS(sts)
  31. return self.returncode

关于断言的简单说明:(别泛滥)

如果条件为真,它什么都不做,反之它触发一个带可选错误信息的AssertionError

  1. def test(a, b):
  2. assert b != 0, "哥哥,分母不能为0啊"
  3. return a / b
  4.  
  5. def main():
  6. test(1, 0)
  7.  
  8. if __name__ == '__main__':
  9. main()

结果:

  1. Traceback (most recent call last):
  2. File "0.assert.py", line 11, in <module>
  3. main()
  4. File "0.assert.py", line 7, in main
  5. test(1, 0)
  6. File "0.assert.py", line 2, in test
  7. assert b != 0, "哥哥,分母不能为0啊"
  8. AssertionError: 哥哥,分母不能为0

运行的时候可以指定-O参数来忽略assert,eg:

python3 -O 0.assert.py

  1. Traceback (most recent call last):
  2. File "0.assert.py", line 11, in <module>
  3. main()
  4. File "0.assert.py", line 7, in main
  5. test(1, 0)
  6. File "0.assert.py", line 3, in test
  7. return a / b
  8. ZeroDivisionError: division by zero

扩展:

https://docs.python.org/3/library/unittest.html

https://www.cnblogs.com/shangren/p/8038935.html


1.2.进程池

多个进程就不需要自己手动去管理了,有Pool来帮你完成,先看个案例:

  1. import os
  2. import time
  3. from multiprocessing import Pool # 首字母大写
  4.  
  5. def test(name):
  6. print("[子进程-%s]PID=%d,PPID=%d" % (name, os.getpid(), os.getppid()))
  7. time.sleep(1)
  8.  
  9. def main():
  10. print("[父进程]PID=%d,PPID=%d" % (os.getpid(), os.getppid()))
  11. p = Pool(5) # 设置最多5个进程(不设置就默认为CPU核数)
  12. for i in range(10):
  13. # 异步执行
  14. p.apply_async(test, args=(i, )) # 同步用apply(如非必要不建议用)
  15. p.close() # 关闭池,不再加入新任务
  16. p.join() # 等待所有子进程执行完毕回收资源(join可以指定超时时间,eg:`p.join(1)`)
  17. print("over")
  18.  
  19. if __name__ == '__main__':
  20. main()

图示:(join可以指定超时时间,eg:p.join(1)

调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process(下面会说为什么)


1.3.源码拓展

验证一下Pool的默认大小是CPU的核数,看源码:

multiprocessing.pool.py

  1. # https://github.com/python/cpython/blob/3.7/Lib/multiprocessing/pool.py
  2. # https://github.com/lotapp/cpython3/tree/master/Lib/multiprocessing/pool.py
  3. class Pool(object):
  4. def __init__(self, processes=指定的进程数,...):
  5. if processes is None:
  6. processes = os.cpu_count() or 1 # os.cpu_count() ~ CPU的核数

源码里面apply_async方法,是有回调函数(callback)的

  1. def apply_async(self,func,args=(),kwds={},callback=None,error_callback=None):
  2. if self._state != RUN:
  3. raise ValueError("Pool not running")
  4. result = ApplyResult(self._cache, callback, error_callback)
  5. self._taskqueue.put(([(result._job, 0, func, args, kwds)], None))
  6. return result

来看个例子:(和JQ很像)

  1. import os
  2. import time
  3. from multiprocessing import Pool # 首字母大写
  4.  
  5. def test(name):
  6. print("[子进程%s]PID=%d,PPID=%d" % (name, os.getpid(), os.getppid()))
  7. time.sleep(1)
  8. return name
  9.  
  10. def error_test(name):
  11. print("[子进程%s]PID=%d,PPID=%d" % (name, os.getpid(), os.getppid()))
  12. raise Exception("[子进程%s]啊,我挂了~" % name)
  13.  
  14. def callback(result):
  15. """成功之后的回调函数"""
  16. print("[子进程%s]执行完毕" % result) # 没有返回值就为None
  17.  
  18. def error_callback(msg):
  19. """错误之后的回调函数"""
  20. print(msg)
  21.  
  22. def main():
  23. print("[父进程]PID=%d,PPID=%d" % (os.getpid(), os.getppid()))
  24. p = Pool() # CPU默认核数
  25. for i in range(5):
  26. # 搞2个出错的看看
  27. if i > 2:
  28. p.apply_async(
  29. error_test,
  30. args=(i, ),
  31. callback=callback,
  32. error_callback=error_callback) # 异步执行
  33. else:
  34. # 异步执行,成功后执行callback函数(有点像jq)
  35. p.apply_async(test, args=(i, ), callback=callback)
  36. p.close() # 关闭池,不再加入新任务
  37. p.join() # 等待所有子进程执行完毕回收资源
  38. print("over")
  39.  
  40. if __name__ == '__main__':
  41. main()

输出:

  1. [父进程]PID=12348PPID=10999
  2. [子进程0]PID=12349PPID=12348
  3. [子进程2]PID=12351PPID=12348
  4. [子进程1]PID=12350PPID=12348
  5. [子进程3]PID=12352PPID=12348
  6. [子进程4]PID=12352PPID=12348
  7. [子进程3]啊,我挂了~
  8. [子进程4]啊,我挂了~
  9. [子进程0]执行完毕
  10. [子进程2]执行完毕
  11. [子进程1]执行完毕
  12. over
 

接着上面继续拓展,补充说说获取函数返回值。上面是通过成功后的回调函数来获取返回值,这次说说自带的方法:

  1. import time
  2. from multiprocessing import Pool, TimeoutError
  3.  
  4. def test(x):
  5. """开平方"""
  6. time.sleep(1)
  7. return x * x
  8.  
  9. def main():
  10. pool = Pool()
  11. task = pool.apply_async(test, (10, ))
  12. print(task)
  13. try:
  14. print(task.get(timeout=1))
  15. except TimeoutError as ex:
  16. print("超时了~", ex)
  17.  
  18. if __name__ == '__main__':
  19. main()

输出:(apply_async返回一个ApplyResult类,里面有个get方法可以获取返回值)

  1. <multiprocessing.pool.ApplyResult object at 0x7fbc354f50b8>
  2. 超时了~

再举个例子,顺便把Pool里面的mapimap方法搞个案例(类比jq)

  1. import time
  2. from multiprocessing import Pool
  3.  
  4. def test(x):
  5. return x * x
  6.  
  7. if __name__ == '__main__':
  8. with Pool(processes=4) as pool:
  9. task = pool.apply_async(test, (10, ))
  10. print(task.get(timeout=1))
  11.  
  12. obj_list = pool.map(test, range(10))
  13. print(obj_list)
  14. # 返回一个可迭代类的实例对象
  15. obj_iter = pool.imap(test, range(10))
  16. print(obj_iter)
  17. next(obj_iter)
  18. for i in obj_iter:
  19. print(i, end=" ")

输出:

  1. 100
  2. [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  3. <multiprocessing.pool.IMapIterator object at 0x7ff7f9734198>
  4. 1 4 9 16 25 36 49 64 81

微微看一眼源码:(基础忘了可以查看==> 点我 )

  1. class IMapIterator(object):
  2. def __init__(self, cache):
  3. self._cond = threading.Condition(threading.Lock())
  4. self._job = next(job_counter)
  5. self._cache = cache
  6. self._items = collections.deque()
  7. self._index = 0
  8. self._length = None
  9. self._unsorted = {}
  10. cache[self._job] = self
  11.  
  12. def __iter__(self):
  13. return self # 返回一个迭代器
  14.  
  15. # 实现next方法
  16. def next(self, timeout=None):
  17. with self._cond:
  18. try:
  19. item = self._items.popleft()
  20. except IndexError:
  21. if self._index == self._length:
  22. raise StopIteration from None
  23. self._cond.wait(timeout)
  24. try:
  25. item = self._items.popleft()
  26. except IndexError:
  27. if self._index == self._length:
  28. raise StopIteration from None
  29. raise TimeoutError from None
  30.  
  31. success, value = item
  32. if success:
  33. return value
  34. raise value
  35. ......

扩展:优雅杀死子进程的探讨 https://segmentfault.com/q/1010000005077517


1.4.拓展之subprocess

官方文档:https://docs.python.org/3/library/subprocess.html

还记得之前李代桃僵的execlxxx系列吗?

这不,subprocess就是它的一层封装,当然了要强大的多,先看个例子:(以os.execlp的例子为引)

  1. import subprocess
  2.  
  3. def main():
  4. # os.execlp("ls", "ls", "-al") # 执行Path环境变量可以搜索到的命令
  5. result = subprocess.run(["ls", "-al"])
  6. print(result)
  7.  
  8. if __name__ == '__main__':
  9. main()

输出

  1. 总用量 44
  2. drwxrwxr-x 2 dnt dnt 4096 8 7 17:32 .
  3. drwxrwxr-x 4 dnt dnt 4096 8 6 08:01 ..
  4. -rw-rw-r-- 1 dnt dnt 151 8 3 10:49 0.assert.py
  5. -rw-rw-r-- 1 dnt dnt 723 8 5 18:00 1.process2.py
  6. -rw-rw-r-- 1 dnt dnt 501 8 3 10:20 1.process.py
  7. -rw-rw-r-- 1 dnt dnt 1286 8 6 08:16 2.pool1.py
  8. -rw-rw-r-- 1 dnt dnt 340 8 7 16:38 2.pool2.py
  9. -rw-rw-r-- 1 dnt dnt 481 8 7 16:50 2.pool3.py
  10. -rw-rw-r-- 1 dnt dnt 652 8 5 17:01 2.pool.py
  11. -rw-rw-r-- 1 dnt dnt 191 8 7 17:33 3.subprocess.py
  12. CompletedProcess(args=['ls', '-al'], returncode=0)

文档

现在看下官方的文档描述来理解一下:

  1. r"""
  2. 具有可访问I / O流的子进程
  3. Subprocesses with accessible I/O streams
  4.  
  5. 此模块允许您生成进程,连接到它们输入/输出/错误管道,并获取其返回代码。
  6. This module allows you to spawn processes, connect to their
  7. input/output/error pipes, and obtain their return codes.
  8.  
  9. 完整文档可以查看:https://docs.python.org/3/library/subprocess.html
  10. For a complete description of this module see the Python documentation.
  11.  
  12. Main API
  13. ========
  14. run(...): 运行命令,等待它完成,然后返回`CompletedProcess`实例。
  15. Runs a command, waits for it to complete,
  16. then returns a CompletedProcess instance.
  17.  
  18. Popen(...): 用于在新进程中灵活执行命令的类
  19. A class for flexibly executing a command in a new process
  20.  
  21. Constants(常量)
  22. ---------
  23. DEVNULL: 特殊值,表示应该使用`os.devnull`
  24. Special value that indicates that os.devnull should be used
  25.  
  26. PIPE: 表示应创建`PIPE`管道的特殊值
  27. Special value that indicates a pipe should be created
  28.  
  29. STDOUT: 特殊值,表示`stderr`应该转到`stdout`
  30. Special value that indicates that stderr should go to stdout
  31.  
  32. Older API(尽量不用,说不定以后就淘汰了)
  33. =========
  34. call(...): 运行命令,等待它完成,然后返回返回码。
  35. Runs a command, waits for it to complete, then returns the return code.
  36.  
  37. check_call(...): Same as call() but raises CalledProcessError()
  38. if return code is not 0(返回值不是0就引发异常)
  39.  
  40. check_output(...): 与check_call()相同,但返回`stdout`的内容,而不是返回代码
  41. Same as check_call but returns the contents of stdout instead of a return code
  42.  
  43. getoutput(...): 在shell中运行命令,等待它完成,然后返回输出
  44. Runs a command in the shell, waits for it to complete,then returns the output
  45.  
  46. getstatusoutput(...): 在shell中运行命令,等待它完成,然后返回一个(exitcode,output)元组
  47. Runs a command in the shell, waits for it to complete,
  48. then returns a (exitcode, output) tuple
  49. """

其实看看源码很有意思:(内部其实就是调用的os.popen【进程先导篇讲进程守护的时候用过】)

  1. def run(*popenargs, input=None, capture_output=False,
  2. timeout=None, check=False, **kwargs):
  3.  
  4. if input is not None:
  5. if 'stdin' in kwargs:
  6. raise ValueError('stdin和输入参数可能都不会被使用。')
  7. kwargs['stdin'] = PIPE
  8.  
  9. if capture_output:
  10. if ('stdout' in kwargs) or ('stderr' in kwargs):
  11. raise ValueError('不能和capture_outpu一起使用stdout 或 stderr')
  12. kwargs['stdout'] = PIPE
  13. kwargs['stderr'] = PIPE
  14.  
  15. with Popen(*popenargs, **kwargs) as process:
  16. try:
  17. stdout, stderr = process.communicate(input, timeout=timeout)
  18. except TimeoutExpired:
  19. process.kill()
  20. stdout, stderr = process.communicate()
  21. raise TimeoutExpired(
  22. process.args, timeout, output=stdout, stderr=stderr)
  23. except: # 包括KeyboardInterrupt的通信处理。
  24. process.kill()
  25. # 不用使用process.wait(),.__ exit__为我们做了这件事。
  26. raise
  27. retcode = process.poll()
  28. if check and retcode:
  29. raise CalledProcessError(
  30. retcode, process.args, output=stdout, stderr=stderr)
  31. return CompletedProcess(process.args, retcode, stdout, stderr)

返回值类型:CompletedProcess

  1. # https://github.com/lotapp/cpython3/blob/master/Lib/subprocess.py
  2. class CompletedProcess(object):
  3. def __init__(self, args, returncode, stdout=None, stderr=None):
  4. self.args = args
  5. self.returncode = returncode
  6. self.stdout = stdout
  7. self.stderr = stderr
  8.  
  9. def __repr__(self):
  10. """对象按指定的格式显示"""
  11. args = [
  12. 'args={!r}'.format(self.args),
  13. 'returncode={!r}'.format(self.returncode)
  14. ]
  15. if self.stdout is not None:
  16. args.append('stdout={!r}'.format(self.stdout))
  17. if self.stderr is not None:
  18. args.append('stderr={!r}'.format(self.stderr))
  19. return "{}({})".format(type(self).__name__, ', '.join(args))
  20.  
  21. def check_returncode(self):
  22. """如果退出代码非零,则引发CalledProcessError"""
  23. if self.returncode:
  24. raise CalledProcessError(self.returncode, self.args, self.stdout,
  25. self.stderr)

简单demo

再来个案例体会一下方便之处:

  1. import subprocess
  2.  
  3. def main():
  4. result = subprocess.run(["ping", "www.baidu.com"])
  5. print(result.stdout)
  6.  
  7. if __name__ == '__main__':
  8. main()

图示:

交互demo

再来个强大的案例(交互的程序都可以,比如 ftpnslookup 等等):popen1.communicate

  1. import subprocess
  2.  
  3. def main():
  4. process = subprocess.Popen(
  5. ["ipython3"],
  6. stdin=subprocess.PIPE,
  7. stdout=subprocess.PIPE,
  8. stderr=subprocess.PIPE)
  9. try:
  10. # 对pstree进行交互
  11. out, err = process.communicate(input=b'print("hello")', timeout=3)
  12. print("Out:%s\nErr:%s" % (out.decode(), err.decode()))
  13. except TimeoutError:
  14. # 如果超时到期,则子进程不会被终止,需要自己处理一下
  15. process.kill()
  16. out, err = process.communicate()
  17. print("Out:%s\nErr:%s" % (out.decode(), err.decode()))
  18.  
  19. if __name__ == '__main__':
  20. main()

输出:

  1. IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.
  2. In [1]: hello
  3. In [2]: Do you really want to exit ([y]/n)?
  4. Err:

注意点:如果超时到期,则子进程不会被终止,需要自己处理一下(官方提醒)

通信demo

这个等会说进程间通信还会说,所以简单举个例子,老规矩拿ps aux | grep bash说事:

  1. import subprocess
  2.  
  3. def main():
  4. # ps aux | grep bash
  5. # 进程1获取结果
  6. p1 = subprocess.Popen(["ps", "-aux"], stdout=subprocess.PIPE)
  7. # 得到进程1的结果再进行筛选
  8. p2 = subprocess.Popen(["grep", "bash"], stdin=p1.stdout, stdout=subprocess.PIPE)
  9. # 关闭写段(结果已经获取到进程2中了,防止干扰显示)
  10. p1.stdout.close()
  11. # 与流程交互:将数据发送到stdin并关闭它。
  12. msg_tuple = p2.communicate()
  13. # 输出结果
  14. print(msg_tuple[0].decode())
  15.  
  16. if __name__ == '__main__':
  17. main()

输出:(以前案例:进程间通信~PIPE匿名管道

  1. dnt 2470 0.0 0.1 24612 5236 pts/0 Ss 06:01 0:00 bash
  2. dnt 2512 0.0 0.1 24744 5760 pts/1 Ss 06:02 0:00 bash
  3. dnt 20784 0.0 0.1 24692 5588 pts/2 Ss+ 06:21 0:00 /bin/bash
  4. dnt 22377 0.0 0.0 16180 1052 pts/1 S+ 06:30 0:00 grep bash

其他扩展可以看看这篇文章:subprocess与Popen()

 

1.5.进程间通信~PIPE管道通信

这个比较有意思,看个案例:

  1. from multiprocessing import Process, Pipe
  2.  
  3. def test(w):
  4. w.send("[子进程]老爸,老妈回来记得喊我一下~")
  5. msg = w.recv()
  6. print(msg)
  7.  
  8. def main():
  9. r, w = Pipe()
  10. p1 = Process(target=test, args=(w, ))
  11. p1.start()
  12. msg = r.recv()
  13. print(msg)
  14. r.send("[父进程]滚犊子,赶紧写作业,不然我得跪方便面!")
  15. p1.join()
  16.  
  17. if __name__ == '__main__':
  18. main()

结果:

  1. 老爸,老妈回来记得喊我一下~
  2. 滚犊子,赶紧写作业,不然我得跪方便面!

multiprocessing.Pipe源码分析

按照道理应该子进程自己写完自己读了,和上次讲得不一样啊?不急,先看看源码:

  1. # https://github.com/lotapp/cpython3/blob/master/Lib/multiprocessing/context.py
  2. def Pipe(self, duplex=True):
  3. '''返回由管道连接的两个连接对象'''
  4. from .connection import Pipe
  5. return Pipe(duplex)

看看connection.Pipe方法的定义部分,是不是双向通信就看你是否设置duplex=True

  1. # https://github.com/lotapp/cpython3/blob/master/Lib/multiprocessing/connection.py
  2. if sys.platform != 'win32':
  3. def Pipe(duplex=True):
  4. '''返回管道两端的一对连接对象'''
  5. if duplex:
  6. # 双工内部其实是socket系列(下次讲)
  7. s1, s2 = socket.socketpair()
  8. s1.setblocking(True)
  9. s2.setblocking(True)
  10. c1 = Connection(s1.detach())
  11. c2 = Connection(s2.detach())
  12. else:
  13. # 这部分就是我们上次讲的pipe管道
  14. fd1, fd2 = os.pipe()
  15. c1 = Connection(fd1, writable=False)
  16. c2 = Connection(fd2, readable=False)
  17. return c1, c2
  18. else:
  19. def Pipe(duplex=True):
  20. # win平台的一系列处理
  21. ......
  22. c1 = PipeConnection(h1, writable=duplex)
  23. c2 = PipeConnection(h2, readable=duplex)
  24. return c1, c2

通过源码知道了,原来双工是通过socket搞的啊~

再看个和原来一样效果的案例:(不用关来关去的了,方便!)

  1. from multiprocessing import Process, Pipe
  2.  
  3. def test(w):
  4. # 只能写
  5. w.send("[子进程]老爸,咱们完了,老妈一直在门口~")
  6.  
  7. def main():
  8. r, w = Pipe(duplex=False)
  9. p1 = Process(target=test, args=(w, ))
  10. p1.start() # 你把这个放在join前面就直接死锁了
  11. msg = r.recv() # 只能读
  12. print(msg)
  13. p1.join()
  14.  
  15. if __name__ == '__main__':
  16. main()

输出:(可以思考下为什么start换个位置就死锁,提示:阻塞读写

  1. [子进程]老爸,咱们完了,老妈一直在门口~

再举个Pool的例子,咱们就进入今天的重点了:

  1. from multiprocessing import Pipe, Pool
  2.  
  3. def proc_test1(conn):
  4. conn.send("[小明]小张,今天哥们要见一女孩,你陪我呗,我24h等你回复哦~")
  5. msg = conn.recv()
  6. print(msg)
  7.  
  8. def proc_test2(conn):
  9. msg = conn.recv()
  10. print(msg)
  11. conn.send("[小张]不去,万一被我帅气的外表迷倒就坑了~")
  12.  
  13. def main():
  14. conn1, conn2 = Pipe()
  15. p = Pool()
  16. p.apply_async(proc_test1, (conn1, ))
  17. p.apply_async(proc_test2, (conn2, ))
  18. p.close() # 关闭池,不再接收新任务
  19. p.join() # 等待回收,必须先关才能join,不然会异常
  20.  
  21. if __name__ == '__main__':
  22. main()

输出:

  1. [小明]小张,今天哥们要见一女孩,你陪我呗,我24h等你回复哦~
  2. [小张]不去,万一被我帅气的外表迷倒就坑了~

pool.join源码分析

看看源码就理解了:看看Pool的join是啥情况?看源码:

  1. # https://github.com/python/cpython/blob/3.7/Lib/multiprocessing/pool.py
  2. # https://github.com/lotapp/cpython3/blob/master/Lib/multiprocessing/pool.py
  3. def join(self):
  4. util.debug('joining pool')
  5. if self._state == RUN:
  6. # 没关闭就join,这边就会抛出一个异常
  7. raise ValueError("Pool is still running")
  8. elif self._state not in (CLOSE, TERMINATE):
  9. raise ValueError("In unknown state")
  10. self._worker_handler.join()
  11. self._task_handler.join()
  12. self._result_handler.join()
  13. for p in self._pool:
  14. p.join() # 循环join回收

在pool的__init__的方法中,这几个属性:

  1. self._processes = processes # 指定的进程数
  2. self._pool = [] # 列表
  3. self._repopulate_pool() # 给列表append内容的方法

将池进程的数量增加到指定的数量,join的时候会使用这个列表

  1. def _repopulate_pool(self):
  2. # 指定进程数-当前进程数,差几个补几个
  3. for i in range(self._processes - len(self._pool)):
  4. w = self.Process(target=worker,
  5. args=(self._inqueue, self._outqueue,
  6. self._initializer,
  7. self._initargs, self._maxtasksperchild,
  8. self._wrap_exception)
  9. )
  10. self._pool.append(w) # 重点来了
  11. w.name = w.name.replace('Process', 'PoolWorker')
  12. w.daemon = True # pool退出后,通过pool创建的进程都会退出
  13. w.start()
  14. util.debug('added worker')

注意:池的方法只能由创建它的进程使用


1.5.进程间通信~Queue管道通信(常用)

一步步的设局,从底层的的pipe()->os.pipe->PIPE,现在终于到Queue了,心酸啊,明知道上面两个项目

里面基本上不会用,但为了你们能看懂源码,说了这么久%>_<%其实以后当我们从Queue说到MQRPC之后,现在

讲得这些进程间通信(IPC)也基本上不会用了,但本质你得清楚,我尽量多分析点源码,这样你们以后看开源项目压力会很小

欢迎批评指正~

引入案例

  1. from multiprocessing import Process, Queue
  2.  
  3. def test(q):
  4. q.put("[子进程]老爸,我出去嗨了")
  5. print(q.get())
  6.  
  7. def main():
  8. q = Queue()
  9. p = Process(target=test, args=(q, ))
  10. p.start()
  11. msg = q.get()
  12. print(msg)
  13. q.put("[父进程]去吧比卡丘~")
  14. p.join()
  15.  
  16. if __name__ == '__main__':
  17. main()

输出:(getput默认是阻塞等待的)

  1. [子进程]老爸,我出去嗨了
  2. [父进程]去吧比卡丘~

源码拓展

先看看Queue的初始化方法:(不指定大小就是最大队列数)

  1. # 队列类型,使用PIPE,缓存,线程
  2. class Queue(object):
  3. # ctx = multiprocessing.get_context("xxx")
  4. # 上下文总共3种:spawn、fork、forkserver(扩展部分会提一下)
  5. def __init__(self, maxsize=0, *, ctx):
  6. # 默认使用最大容量
  7. if maxsize <= 0:
  8. from .synchronize import SEM_VALUE_MAX as maxsize
  9. self._maxsize = maxsize # 指定队列大小
  10. # 创建了一个PIPE匿名管道(单向)
  11. self._reader, self._writer = connection.Pipe(duplex=False)
  12. # `multiprocessing/synchronize.py > Lock`
  13. self._rlock = ctx.Lock() # 进程锁(读)【非递归】
  14. self._opid = os.getpid() # 获取PID
  15. if sys.platform == 'win32':
  16. self._wlock = None
  17. else:
  18. self._wlock = ctx.Lock() # 进程锁(写)【非递归】
  19. # Semaphore信号量通常用于保护容量有限的资源
  20. # 控制信号量,超了就异常
  21. self._sem = ctx.BoundedSemaphore(maxsize)
  22. # 不忽略PIPE管道破裂的错误
  23. self._ignore_epipe = False
  24. # 线程相关操作
  25. self._after_fork()
  26. # 向`_afterfork_registry`字典中注册
  27. if sys.platform != 'win32':
  28. register_after_fork(self, Queue._after_fork)

关于getput是阻塞的问题,看下源码探探究竟:

q.get():收消息

  1. def get(self, block=True, timeout=None):
  2. # 默认情况是阻塞(lock加锁)
  3. if block and timeout is None:
  4. with self._rlock:
  5. res = self._recv_bytes()
  6. self._sem.release() # 信号量+1
  7. else:
  8. if block:
  9. deadline = time.monotonic() + timeout
  10. # 超时抛异常
  11. if not self._rlock.acquire(block, timeout):
  12. raise Empty
  13. try:
  14. if block:
  15. timeout = deadline - time.monotonic()
  16. # 不管有没有内容都去读,超时就抛异常
  17. if not self._poll(timeout):
  18. raise Empty
  19. elif not self._poll():
  20. raise Empty
  21. # 接收字节数据作为字节对象
  22. res = self._recv_bytes()
  23. self._sem.release() # 信号量+1
  24. finally:
  25. # 释放锁
  26. self._rlock.release()
  27. # 释放锁后,重新序列化数据
  28. return _ForkingPickler.loads(res)

queue.put():发消息

  1. def put(self, obj, block=True, timeout=None):
  2. # 如果Queue已经关闭就抛异常
  3. assert not self._closed, "Queue {0!r} has been closed".format(self)
  4. # 记录信号量的锁
  5. if not self._sem.acquire(block, timeout):
  6. raise Full # 超过数量,抛个异常
  7. # 条件变量允许一个或多个线程等待,直到另一个线程通知它们
  8. with self._notempty:
  9. if self._thread is None:
  10. self._start_thread()
  11. self._buffer.append(obj)
  12. self._notempty.notify()

非阻塞get_nowaitput_nowait本质其实也是调用了getput方法:

  1. def get_nowait(self):
  2. return self.get(False)
  3.  
  4. def put_nowait(self, obj):
  5. return self.put(obj, False)

进程间通信1

说这么多不如来个例子看看:

  1. from multiprocessing import Queue
  2.  
  3. def main():
  4. q = Queue(3) # 只能 put 3条消息
  5. q.put([1, 2, 3, 4]) # put一个List类型的消息
  6. q.put({"a": 1, "b": 2}) # put一个Dict类型的消息
  7. q.put({1, 2, 3, 4}) # put一个Set类型的消息
  8.  
  9. try:
  10. # 不加timeout,就一直阻塞,等消息队列有空位才能发出去
  11. q.put("再加条消息呗", timeout=2)
  12. # Full(Exception)是空实现,你可以直接用Exception
  13. except Exception:
  14. print("消息队列已满,队列数%s,当前存在%s条消息" % (q._maxsize, q.qsize()))
  15.  
  16. try:
  17. # 非阻塞,不能put就抛异常
  18. q.put_nowait("再加条消息呗") # 相当于q.put(obj,False)
  19. except Exception:
  20. print("消息队列已满,队列数%s,当前存在%s条消息" % (q._maxsize, q.qsize()))
  21.  
  22. while not q.empty():
  23. print("队列数:%s,当前存在%s条消息 内容%s" % (q._maxsize, q.qsize(), q.get_nowait()))
  24.  
  25. print("队列数:%s,当前存在:%s条消息" % (q._maxsize, q.qsize()))
  26.  
  27. if __name__ == '__main__':
  28. main()

输出:

  1. 消息队列已满,队列数3,当前存在3条消息
  2. 消息队列已满,队列数3,当前存在3条消息
  3. 队列数:3,当前存在3条消息 内容[1, 2, 3, 4]
  4. 队列数:3,当前存在2条消息 内容{'a': 1, 'b': 2}
  5. 队列数:3,当前存在1条消息 内容{1, 2, 3, 4}
  6. 队列数:3,当前存在:0条消息

补充说明一下:

  1. q._maxsize 队列数(尽量不用_开头的属性和方法)
  2. q.qsize()查看当前队列中存在几条消息
  3. q.full()查看是否满了
  4. q.empty()查看是否为空

再看个简单点的子进程间通信:(铺垫demo)

  1. import os
  2. import time
  3. from multiprocessing import Process, Queue
  4.  
  5. def pro_test1(q):
  6. print("[子进程1]PPID=%d,PID=%d,GID=%d"%(os.getppid(), os.getpid(), os.getgid()))
  7. q.put("[子进程1]小明,今晚撸串不?")
  8.  
  9. # 设置一个简版的重试机制(三次重试)
  10. for i in range(3):
  11. if not q.empty():
  12. print(q.get())
  13. break
  14. else:
  15. time.sleep((i + 1) * 2) # 第一次1s,第二次4s,第三次6s
  16.  
  17. def pro_test2(q):
  18. print("[子进程2]PPID=%d,PID=%d,GID=%d"%(os.getppid(), os.getpid(), os.getgid()))
  19. print(q.get())
  20. time.sleep(4) # 模拟一下网络延迟
  21. q.put("[子进程2]不去,我今天约了妹子")
  22.  
  23. def main():
  24. queue = Queue()
  25. p1 = Process(target=pro_test1, args=(queue, ))
  26. p2 = Process(target=pro_test2, args=(queue, ))
  27. p1.start()
  28. p2.start()
  29. p1.join()
  30. p2.join()
  31.  
  32. if __name__ == '__main__':
  33. main()

输出:(time python3 5.queue2.py

  1. [子进程1]PPID=15220,PID=15221,GID=1000
  2. [子进程2]PPID=15220,PID=15222,GID=1000
  3. [子进程1]小明,今晚撸串不?
  4. [子进程2]不去,我今天约了妹子
  5. real 0m6.087s
  6. user 0m0.053s
  7. sys 0m0.035s

进程间通信2

多进程基本上都是用pool,可用上面说的Queue方法怎么报错了?

  1. import os
  2. import time
  3. from multiprocessing import Pool, Queue
  4.  
  5. def error_callback(msg):
  6. print(msg)
  7.  
  8. def pro_test1(q):
  9. print("[子进程1]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  10. os.getgid()))
  11. q.put("[子进程1]小明,今晚撸串不?")
  12.  
  13. # 设置一个简版的重试机制(三次重试)
  14. for i in range(3):
  15. if not q.empty():
  16. print(q.get())
  17. break
  18. else:
  19. time.sleep((i + 1) * 2) # 第一次1s,第二次4s,第三次6s
  20.  
  21. def pro_test2(q):
  22. print("[子进程2]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  23. os.getgid()))
  24. print(q.get())
  25. time.sleep(4) # 模拟一下网络延迟
  26. q.put("[子进程2]不去,我今天约了妹子")
  27.  
  28. def main():
  29. print("[父进程]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  30. os.getgid()))
  31. queue = Queue()
  32. p = Pool()
  33. p.apply_async(pro_test1, args=(queue, ), error_callback=error_callback)
  34. p.apply_async(pro_test2, args=(queue, ), error_callback=error_callback)
  35. p.close()
  36. p.join()
  37.  
  38. if __name__ == '__main__':
  39. main()

输出:(无法将multiprocessing.Queue对象传递给Pool方法)

  1. [父进程]PPID=4223,PID=32170,GID=1000
  2. Queue objects should only be shared between processes through inheritance
  3. Queue objects should only be shared between processes through inheritance
  4. real 0m0.183s
  5. user 0m0.083s
  6. sys 0m0.012s

下面会详说,先看一下正确方式:(队列换了一下,其他都一样Manager().Queue()

  1. import os
  2. import time
  3. from multiprocessing import Pool, Manager
  4.  
  5. def error_callback(msg):
  6. print(msg)
  7.  
  8. def pro_test1(q):
  9. print("[子进程1]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  10. os.getgid()))
  11. q.put("[子进程1]小明,今晚撸串不?")
  12.  
  13. # 设置一个简版的重试机制(三次重试)
  14. for i in range(3):
  15. if not q.empty():
  16. print(q.get())
  17. break
  18. else:
  19. time.sleep((i + 1) * 2) # 第一次1s,第二次4s,第三次6s
  20.  
  21. def pro_test2(q):
  22. print("[子进程2]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  23. os.getgid()))
  24. print(q.get())
  25. time.sleep(4) # 模拟一下网络延迟
  26. q.put("[子进程2]不去,我今天约了妹子")
  27.  
  28. def main():
  29. print("[父进程]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  30. os.getgid()))
  31. queue = Manager().Queue()
  32. p = Pool()
  33. p.apply_async(pro_test1, args=(queue, ), error_callback=error_callback)
  34. p.apply_async(pro_test2, args=(queue, ), error_callback=error_callback)
  35. p.close()
  36. p.join()
  37.  
  38. if __name__ == '__main__':
  39. main()

输出:

  1. [父进程]PPID=4223,PID=31329,GID=1000
  2. [子进程1]PPID=31329,PID=31335,GID=1000
  3. [子进程2]PPID=31329,PID=31336,GID=1000
  4. [子进程1]小明,今晚撸串不?
  5. [子进程2]不去,我今天约了妹子
  6. real 0m6.134s
  7. user 0m0.133s
  8. sys 0m0.035s

再抛个思考题:(Linux)

  1. import os
  2. import time
  3. from multiprocessing import Pool, Queue
  4.  
  5. def error_callback(msg):
  6. print(msg)
  7.  
  8. q = Queue()
  9.  
  10. def pro_test1():
  11. global q
  12. print("[子进程1]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  13. os.getgid()))
  14. q.put("[子进程1]小明,今晚撸串不?")
  15. # 设置一个简版的重试机制(三次重试)
  16. for i in range(3):
  17. if not q.empty():
  18. print(q.get())
  19. break
  20. else:
  21. time.sleep((i + 1) * 2) # 第一次1s,第二次4s,第三次6s
  22.  
  23. def pro_test2():
  24. global q
  25. print("[子进程2]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  26. os.getgid()))
  27. print(q.get())
  28. time.sleep(4) # 模拟一下网络延迟
  29. q.put("[子进程2]不去,我今天约了妹子")
  30.  
  31. def main():
  32. print("[父进程]PPID=%d,PID=%d,GID=%d" % (os.getppid(), os.getpid(),
  33. os.getgid()))
  34. q = Queue()
  35. p = Pool()
  36. p.apply_async(pro_test1, error_callback=error_callback)
  37. p.apply_async(pro_test2, error_callback=error_callback)
  38. p.close()
  39. p.join()
  40.  
  41. if __name__ == '__main__':
  42. main()

输出:(为啥这样也可以【提示:fork】)

  1. [父进程]PPID=12855,PID=16879,GID=1000
  2. [子进程1]PPID=16879,PID=16880,GID=1000
  3. [子进程2]PPID=16879,PID=16881,GID=1000
  4. [子进程1]小明,今晚撸串不?
  5. [子进程2]不去,我今天约了妹子
  6. real 0m6.120s
  7. user 0m0.105s
  8. sys 0m0.024s

进程拓展

官方参考:https://docs.python.org/3/library/multiprocessing.html

1.上下文系

  1. spawn:(Win默认,Linux下也可以用【>=3.4】)
    1. 父进程启动一个新的python解释器进程。
    2. 子进程只会继承运行进程对象run()方法所需的那些资源。
    3. 不会继承父进程中不必要的文件描述符和句柄。
    4. 与使用fork或forkserver相比,使用此方法启动进程相当慢。
    5. 可在Unix和Windows上使用。Windows上的默认设置。
  2. fork:(Linux下默认)
    1. 父进程用于os.fork()分叉Python解释器。
    2. 子进程在开始时与父进程相同(这时候内部变量之类的还没有被修改)
    3. 父进程的所有资源都由子进程继承(用到多线程的时候可能有些问题)
    4. 仅适用于Unix。Unix上的默认值。
  3. forkserver:(常用)
    1. 当程序启动并选择forkserver start方法时,将启动服务器进程。
    2. 从那时起,每当需要一个新进程时,父进程就会连接到服务器并请求它分叉一个新进程。
    3. fork服务器进程是单线程的,因此它可以安全使用os.fork()。没有不必要的资源被继承。
    4. 可在Unix平台上使用,支持通过Unix管道传递文件描述符。

这块官方文档很详细,贴下官方的2个案例:

通过multiprocessing.set_start_method(xxx)来设置启动的上下文类型

  1. import multiprocessing as mp
  2.  
  3. def foo(q):
  4. q.put('hello')
  5.  
  6. if __name__ == '__main__':
  7. mp.set_start_method('spawn') # 不要过多使用
  8. q = mp.Queue()
  9. p = mp.Process(target=foo, args=(q,))
  10. p.start()
  11. print(q.get())
  12. p.join()

输出:(set_start_method不要过多使用)

  1. hello
  2. real 0m0.407s
  3. user 0m0.134s
  4. sys 0m0.012s

如果你把设置启动上下文注释掉:(消耗的总时间少了很多)

  1. real 0m0.072s
  2. user 0m0.057s
  3. sys 0m0.016s

也可以通过multiprocessing.get_context(xxx)获取指定类型的上下文

  1. import multiprocessing as mp
  2.  
  3. def foo(q):
  4. q.put('hello')
  5.  
  6. if __name__ == '__main__':
  7. ctx = mp.get_context('spawn')
  8. q = ctx.Queue()
  9. p = ctx.Process(target=foo, args=(q,))
  10. p.start()
  11. print(q.get())
  12. p.join()

输出:(get_context在Python源码里用的比较多,so=>也建议大家这么用)

  1. hello
  2. real 0m0.169s
  3. user 0m0.146s
  4. sys 0m0.024s

从结果来看,总耗时也少了很多


2.日记系列

说下日记相关的事情:

先看下multiprocessing里面的日记记录:

  1. # https://github.com/lotapp/cpython3/blob/master/Lib/multiprocessing/context.py
  2. def log_to_stderr(self, level=None):
  3. '''打开日志记录并添加一个打印到stderr的处理程序'''
  4. from .util import log_to_stderr
  5. return log_to_stderr(level)

更多Loging模块内容可以看官方文档:https://docs.python.org/3/library/logging.html

这个是内部代码,看看即可:

  1. # https://github.com/lotapp/cpython3/blob/master/Lib/multiprocessing/util.py
  2. def log_to_stderr(level=None):
  3. '''打开日志记录并添加一个打印到stderr的处理程序'''
  4. # 全局变量默认是False
  5. global _log_to_stderr
  6. import logging
  7.  
  8. # 日记记录转换成文本
  9. formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT)
  10. # 一个处理程序类,它将已适当格式化的日志记录写入流
  11. handler = logging.StreamHandler() # 此类不会关闭流,因为用到了sys.stdout|sys.stderr
  12. # 设置格式:'[%(levelname)s/%(processName)s] %(message)s'
  13. handler.setFormatter(formatter)
  14.  
  15. # 返回`multiprocessing`专用的记录器
  16. logger = get_logger()
  17. # 添加处理程序
  18. logger.addHandler(handler)
  19.  
  20. if level:
  21. # 设置日记级别
  22. logger.setLevel(level)
  23. # 现在log是输出到stderr的
  24. _log_to_stderr = True
  25. return _logger

Logging之前也有提过,可以看看:https://www.cnblogs.com/dotnetcrazy/p/9333792.html#2.装饰器传参的扩展(可传可不传)

来个案例:

  1. import logging
  2. from multiprocessing import Process, log_to_stderr
  3.  
  4. def test():
  5. print("test")
  6.  
  7. def start_log():
  8. # 把日记输出定向到sys.stderr中
  9. logger = log_to_stderr()
  10. # 设置日记记录级别
  11. # 敏感程度:DEBUG、INFO、WARN、ERROR、CRITICAL
  12. print(logging.WARN == logging.WARNING) # 这两个是一样的
  13. level = logging.INFO
  14. logger.setLevel(level) # 设置日记级别(一般都是WARN)
  15.  
  16. # 自定义输出
  17. # def log(self, level, msg, *args, **kwargs):
  18. logger.log(level, "我是通用格式") # 通用,下面的内部也是调用的这个
  19. logger.info("info 测试")
  20. logger.warning("warning 测试")
  21. logger.error("error 测试")
  22.  
  23. def main():
  24. start_log()
  25. # 做的操作都会被记录下来
  26. p = Process(target=test)
  27. p.start()
  28. p.join()
  29.  
  30. if __name__ == '__main__':
  31. main()

输出:

  1. True
  2. [INFO/MainProcess] 我是通用格式
  3. [INFO/MainProcess] info 测试
  4. [WARNING/MainProcess] warning 测试
  5. [ERROR/MainProcess] error 测试
  6. [INFO/Process-1] child process calling self.run()
  7. test
  8. [INFO/Process-1] process shutting down
  9. [INFO/Process-1] process exiting with exitcode 0
  10. [INFO/MainProcess] process shutting down

3.进程5态

之前忘记说了~现在快结尾了,补充一下进程5态:(来个草图)

 

1.6.进程间状态共享

应该尽量避免进程间状态共享,但需求在那,所以还是得研究,官方推荐了两种方式:

1.共享内存(Value or Array

之前说过Queue:在Process之间使用没问题,用到Pool,就使用Manager().xxxValueArray,就不太一样了:

看看源码:(Manager里面的Array和Process共享的Array不是一个概念,而且也没有同步机制)

  1. # https://github.com/lotapp/cpython3/blob/master/Lib/multiprocessing/managers.py
  2. class Value(object):
  3. def __init__(self, typecode, value, lock=True):
  4. self._typecode = typecode
  5. self._value = value
  6.  
  7. def get(self):
  8. return self._value
  9.  
  10. def set(self, value):
  11. self._value = value
  12.  
  13. def __repr__(self):
  14. return '%s(%r, %r)' % (type(self).__name__, self._typecode, self._value)
  15.  
  16. value = property(get, set) # 给value设置get和set方法(和value的属性装饰器一样效果)
  17.  
  18. def Array(typecode, sequence, lock=True):
  19. return array.array(typecode, sequence)

Process为例看看怎么用:

  1. from multiprocessing import Process, Value, Array
  2.  
  3. def proc_test1(value, array):
  4. print("子进程1", value.value)
  5. array[0] = 10
  6. print("子进程1", array[:])
  7.  
  8. def proc_test2(value, array):
  9. print("子进程2", value.value)
  10. array[1] = 10
  11. print("子进程2", array[:])
  12.  
  13. def main():
  14. try:
  15. value = Value("d", 3.14) # d 类型,相当于C里面的double
  16. array = Array("i", range(10)) # i 类型,相当于C里面的int
  17. print(type(value))
  18. print(type(array))
  19.  
  20. p1 = Process(target=proc_test1, args=(value, array))
  21. p2 = Process(target=proc_test2, args=(value, array))
  22. p1.start()
  23. p2.start()
  24. p1.join()
  25. p2.join()
  26.  
  27. print("父进程", value.value) # 获取值
  28. print("父进程", array[:]) # 获取值
  29. except Exception as ex:
  30. print(ex)
  31. else:
  32. print("No Except")
  33.  
  34. if __name__ == '__main__':
  35. main()

输出:(ValueArray进程|线程安全的)

  1. <class 'multiprocessing.sharedctypes.Synchronized'>
  2. <class 'multiprocessing.sharedctypes.SynchronizedArray'>
  3. 子进程1 3.14
  4. 子进程1 [10, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  5. 子进程2 3.14
  6. 子进程2 [10, 10, 2, 3, 4, 5, 6, 7, 8, 9]
  7. 父进程 3.14
  8. 父进程 [10, 10, 2, 3, 4, 5, 6, 7, 8, 9]
  9. No Except

类型方面的对应关系:

  1. typecode_to_type = {
  2. 'c': ctypes.c_char,
  3. 'u': ctypes.c_wchar,
  4. 'b': ctypes.c_byte,
  5. 'B': ctypes.c_ubyte,
  6. 'h': ctypes.c_short,
  7. 'H': ctypes.c_ushort,
  8. 'i': ctypes.c_int,
  9. 'I': ctypes.c_uint,
  10. 'l': ctypes.c_long,
  11. 'L': ctypes.c_ulong,
  12. 'q': ctypes.c_longlong,
  13. 'Q': ctypes.c_ulonglong,
  14. 'f': ctypes.c_float,
  15. 'd': ctypes.c_double
  16. }

这两个类型其实是ctypes类型,更多的类型可以去` multiprocessing.sharedctypes`查看,来张图: 回头解决GIL的时候会用到C系列或者Go系列的共享库(讲线程的时候会说)


关于进程安全的补充说明:对于原子性操作就不用说,铁定安全,但注意一下i+=1并不是原子性操作:

  1. from multiprocessing import Process, Value
  2.  
  3. def proc_test1(value):
  4. for i in range(1000):
  5. value.value += 1
  6.  
  7. def main():
  8. value = Value("i", 0)
  9. p_list = [Process(target=proc_test1, args=(value, )) for i in range(5)]
  10. # 批量启动
  11. for i in p_list:
  12. i.start()
  13. # 批量资源回收
  14. for i in p_list:
  15. i.join()
  16. print(value.value)
  17.  
  18. if __name__ == '__main__':
  19. main()

输出:(理论上应该是:5×1000=5000)

  1. 2153

稍微改一下才行:(进程安全:只是提供了安全的方法,并不是什么都不用你操心了

  1. # 通用方法
  2. def proc_test1(value):
  3. for i in range(1000):
  4. if value.acquire():
  5. value.value += 1
  6. value.release()
  7.  
  8. # 官方案例:(Lock可以使用with托管)
  9. def proc_test1(value):
  10. for i in range(1000):
  11. with value.get_lock():
  12. value.value += 1
  13.  
  14. # 更多可以查看:`sharedctypes.SynchronizedBase` 源码

输出:(关于锁这块,后面讲线程的时候会详说,看看就好【语法的确比C#麻烦点】)

  1. 5000

看看源码:(之前探讨如何优雅的杀死子进程,其中就有一种方法使用了Value

  1. def Value(typecode_or_type, *args, lock=True, ctx=None):
  2. '''返回Value的同步包装器'''
  3. obj = RawValue(typecode_or_type, *args)
  4. if lock is False:
  5. return obj
  6. # 默认支持Lock
  7. if lock in (True, None):
  8. ctx = ctx or get_context() # 获取上下文
  9. lock = ctx.RLock() # 获取递归锁
  10. if not hasattr(lock, 'acquire'):
  11. raise AttributeError("%r has no method 'acquire'" % lock)
  12. # 一系列处理
  13. return synchronized(obj, lock, ctx=ctx)
  14.  
  15. def Array(typecode_or_type, size_or_initializer, *, lock=True, ctx=None):
  16. '''返回RawArray的同步包装器'''
  17. obj = RawArray(typecode_or_type, size_or_initializer)
  18. if lock is False:
  19. return obj
  20. # 默认是支持Lock的
  21. if lock in (True, None):
  22. ctx = ctx or get_context() # 获取上下文
  23. lock = ctx.RLock() # 递归锁属性
  24. # 查看是否有acquire属性
  25. if not hasattr(lock, 'acquire'):
  26. raise AttributeError("%r has no method 'acquire'" % lock)
  27. return synchronized(obj, lock, ctx=ctx)

扩展部分可以查看这篇文章:http://blog.51cto.com/11026142/1874807


2.服务器进程(Manager

官方文档:https://docs.python.org/3/library/multiprocessing.html#managers

有一个服务器进程负责维护所有的对象,而其他进程连接到该进程,通过代理对象操作服务器进程当中的对象

通过返回的经理Manager()将支持类型list、dict、Namespace、Lock、RLock、Semaphore、BoundedSemaphore、Condition、Event、Barrier、Queue

举个简单例子(后面还会再说):(本质其实就是多个进程通过代理,共同操作服务端内容)

  1. from multiprocessing import Pool, Manager
  2.  
  3. def test1(d, l):
  4. d[1] = '1'
  5. d['2'] = 2
  6. d[0.25] = None
  7. l.reverse()
  8.  
  9. def test2(d, l):
  10. print(d)
  11. print(l)
  12.  
  13. def main():
  14. with Manager() as manager:
  15. dict_test = manager.dict()
  16. list_test = manager.list(range(10))
  17.  
  18. pool = Pool()
  19. pool.apply_async(test1, args=(dict_test, list_test))
  20. pool.apply_async(test2, args=(dict_test, list_test))
  21. pool.close()
  22. pool.join()
  23.  
  24. if __name__ == '__main__':
  25. main()

输出:

  1. {1: '1', '2': 2, 0.25: None}
  2. [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

服务器进程管理器比使用共享内存对象更灵活,因为它们可以支持任意对象类型。此外,单个管理器可以通过网络在不同计算机上的进程共享。但是,它们比使用共享内存慢(毕竟有了“中介”

同步问题依然需要注意一下,举个例子体会一下:

  1. from multiprocessing import Manager, Process, Lock
  2.  
  3. def test(dict1, lock):
  4. for i in range(100):
  5. with lock: # 你可以把这句话注释掉,然后就知道为什么加了
  6. dict1["year"] += 1
  7.  
  8. def main():
  9. with Manager() as m:
  10. lock = Lock()
  11. dict1 = m.dict({"year": 2000})
  12. p_list = [Process(target=test, args=(dict1, lock)) for i in range(5)]
  13. for i in p_list:
  14. i.start()
  15. for i in p_list:
  16. i.join()
  17. print(dict1)
  18.  
  19. if __name__ == '__main__':
  20. main()

扩展补充:

  1. multiprocessing.Lock是一个进程安全对象,因此您可以将其直接传递给子进程并在所有进程中安全地使用它。
  2. 大多数可变Python对象(如list,dict,大多数类)不能保证进程中安全,所以它们在进程间共享时需要使用Manager
  3. 多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下创建进程开销巨大。

Manager这块官方文档很详细,可以看看:https://docs.python.org/3/library/multiprocessing.html#managers

WinServer的可以参考这篇 or 这篇埋坑记(Manager一般都是部署在Linux的,Win的客户端不影响)

扩展补充

还记得之前的:无法将multiprocessing.Queue对象传递给Pool方法吗?其实一般都是这两种方式解决的:

  1. 使用Manager需要生成另一个进程来托管Manager服务器。 并且所有获取/释放锁的调用都必须通过IPC发送到该服务器。
  2. 使用初始化程序在池创建时传递常规multiprocessing.Queue()这将使Queue实例在所有子进程中全局共享

再看一下Pool的__init__方法:

  1. # processes:进程数
  2. # initializer,initargs 初始化进行的操作
  3. # maxtaskperchild:每个进程执行task的最大数目
  4. # contex:上下文对象
  5. def __init__(self, processes=None, initializer=None, initargs=(),
  6. maxtasksperchild=None, context=None):

第一种方法不够轻量级,在讲案例前,稍微说下第二种方法:(也算把上面留下的悬念解了)

  1. import os
  2. import time
  3. from multiprocessing import Pool, Queue
  4.  
  5. def error_callback(msg):
  6. print(msg)
  7.  
  8. def pro_test1():
  9. print("[子进程1]PPID=%d,PID=%d" % (os.getppid(), os.getpid()))
  10. q.put("[子进程1]小明,今晚撸串不?")
  11.  
  12. # 设置一个简版的重试机制(三次重试)
  13. for i in range(3):
  14. if not q.empty():
  15. print(q.get())
  16. break
  17. else:
  18. time.sleep((i + 1) * 2) # 第一次1s,第二次4s,第三次6s
  19.  
  20. def pro_test2():
  21. print("[子进程2]PPID=%d,PID=%d" % (os.getppid(), os.getpid()))
  22. print(q.get())
  23. time.sleep(4) # 模拟一下网络延迟
  24. q.put("[子进程2]不去,我今天约了妹子")
  25.  
  26. def init(queue):
  27. global q
  28. q = queue
  29.  
  30. def main():
  31. print("[父进程]PPID=%d,PID=%d" % (os.getppid(), os.getpid()))
  32. queue = Queue()
  33. p = Pool(initializer=init, initargs=(queue, ))
  34. p.apply_async(pro_test1, error_callback=error_callback)
  35. p.apply_async(pro_test2, error_callback=error_callback)
  36. p.close()
  37. p.join()
  38.  
  39. if __name__ == '__main__':
  40. main()

输出:(就是在初始化Pool的时候,传了初始化执行的方法并传了参数alizer=init, initargs=(queue, ))

  1. [父进程]PPID=13157,PID=24864
  2. [子进程1]PPID=24864,PID=24865
  3. [子进程2]PPID=24864,PID=24866
  4. [子进程1]小明,今晚撸串不?
  5. [子进程2]不去,我今天约了妹子
  6. real 0m6.105s
  7. user 0m0.071s
  8. sys 0m0.042s

Win下亦通用(win下没有os.getgid


1.7.分布式进程的案例

有了1.6的基础,咱们来个例子练练:

BaseManager的缩略图:

服务器端代码:

  1. from multiprocessing import Queue
  2. from multiprocessing.managers import BaseManager
  3.  
  4. def main():
  5. # 用来身份验证的
  6. key = b"8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92"
  7. get_zhang_queue = Queue() # 小张消息队列
  8. get_ming_queue = Queue() # 小明消息队列
  9.  
  10. # 把Queue注册到网络上, callable参数关联了Queue对象
  11. BaseManager.register("get_zhang_queue", callable=lambda: get_zhang_queue)
  12. BaseManager.register("get_ming_queue", callable=lambda: get_ming_queue)
  13.  
  14. # 实例化一个Manager对象。绑定ip+端口, 设置验证秘钥
  15. manager = BaseManager(address=("192.168.36.235", 5438), authkey=key)
  16. # 运行serve
  17. manager.get_server().serve_forever()
  18.  
  19. if __name__ == '__main__':
  20. main()

客户端代码1:

  1. from multiprocessing.managers import BaseManager
  2.  
  3. def main():
  4. """客户端1"""
  5. key = b"8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92"
  6.  
  7. # 注册对应方法的名字(从网络上获取Queue)
  8. BaseManager.register("get_ming_queue")
  9. BaseManager.register("get_zhang_queue")
  10.  
  11. # 实例化一个Manager对象。绑定ip+端口, 设置验证秘钥
  12. m = BaseManager(address=("192.168.36.235", 5438), authkey=key)
  13. # 连接到服务器
  14. m.connect()
  15.  
  16. q1 = m.get_zhang_queue() # 在自己队列里面留言
  17. q1.put("[小张]小明,老大明天是不是去外地办事啊?")
  18.  
  19. q2 = m.get_ming_queue() # 获取小明说的话
  20. print(q2.get())
  21.  
  22. if __name__ == '__main__':
  23. main()

客户端代码2:

  1. from multiprocessing.managers import BaseManager
  2. def main():
  3. """客户端2"""
  4. key = b"8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92"
  5. # 注册对应方法的名字(从网络上获取Queue)
  6. BaseManager.register("get_ming_queue")
  7. BaseManager.register("get_zhang_queue")
  8. # 实例化一个Manager对象。绑定ip+端口, 设置验证秘钥
  9. m = BaseManager(address=("192.168.36.235", 5438), authkey=key)
  10. # 连接到服务器
  11. m.connect()
  12. q1 = m.get_zhang_queue() # 获取小张说的话
  13. print(q1.get())
  14. q2 = m.get_ming_queue() # 在自己队列里面留言
  15. q2.put("[小明]这几天咱们终于可以不加班了(>_<)")
  16. if __name__ == '__main__':
  17. main()

输出图示:

服务器运行在Linux的测试:

其实还有一部分内容没说,明天得出去办点事,先到这吧,后面找机会继续带一下


参考文章:

进程共享的探讨:python-sharing-a-lock-between-processes

多进程锁的探讨:trouble-using-a-lock-with-multiprocessing-pool-pickling-error

JoinableQueue扩展:https://www.cnblogs.com/smallmars/p/7093603.html

Python多进程编程:https://www.cnblogs.com/kaituorensheng/p/4445418.html

有深度但需要辩证看的两篇文章:

跨进程对象共享:http://blog.ftofficer.com/2009/12/python-multiprocessing-3-about-queue

关于Queue:http://blog.ftofficer.com/2009/12/python-multiprocessing-2-object-sharing-across-process

 

NetCore并发编程

 Python的线程、并行、协程下次说

示例代码:https://github.com/lotapp/BaseCode/tree/master/netcore/4_Concurrency

先简单说下概念(其实之前也有说,所以简说下):

  1. 并发:同时做多件事情
  2. 多线程:并发的一种形式
  3. 并行处理:多线程的一种(线程池产生的一种并发类型,eg:异步编程
  4. 响应式编程:一种编程模式,对事件进行响应(有点类似于JQ的事件)

Net里面很少用进程,在以前基本上都是线程+池+异步+并行+协程

我这边简单引入一下,毕竟主要是写Python的教程,Net只是帮你们回顾一下,如果你发现还没听过这些概念,或者你的项目中还充斥着各种ThreadThreadPool的话,真的得系统的学习一下了,现在官网的文档已经很完善了,记得早几年啥都没有,也只能挖那些外国开源项目:

https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-processing-and-concurrency

1.异步编程(Task)

Task的目的其实就是为了简化ThreadThreadPool的代码,下面一起看看吧:

异步用起来比较简单,一般IO,DB,Net用的比较多,很多时候都会采用重试机制,举个简单的例子:

  1. /// <summary>
  2. /// 模拟一个网络操作(别忘了重试机制)
  3. /// </summary>
  4. /// <param name="url">url</param>
  5. /// <returns></returns>
  6. private async static Task<string> DownloadStringAsync(string url)
  7. {
  8. using (var client = new HttpClient())
  9. {
  10. // 设置第一次重试时间
  11. var nextDelay = TimeSpan.FromSeconds(1);
  12. for (int i = 0; i < 3; i++)
  13. {
  14. try
  15. {
  16. return await client.GetStringAsync(url);
  17. }
  18. catch { }
  19. await Task.Delay(nextDelay); // 用异步阻塞的方式防止服务器被太多重试给阻塞了
  20. nextDelay *= 2; // 3次重试机会,第一次1s,第二次2s,第三次4s
  21. }
  22. // 最后一次尝试,错误就抛出
  23. return await client.GetStringAsync(url);
  24. }
  25. }

然后补充说下Task异常的问题,当你await的时候如果有异常会抛出,在第一个await处捕获处理即可

如果asyncawait就是理解不了的可以这样想:async就是为了让await生效(为了向后兼容)

对了,如果返回的是void,你设置成Task就行了,触发是类似于事件之类的方法才使用void,不然没有返回值都是使用Task

项目里经常有这么一个场景:等待一组任务完成后再执行某个操作,看个引入案例:

  1. /// <summary>
  2. /// 1.批量任务
  3. /// </summary>
  4. /// <param name="list"></param>
  5. /// <returns></returns>
  6. private async static Task<string[]> DownloadStringAsync(IEnumerable<string> list)
  7. {
  8. using (var client = new HttpClient())
  9. {
  10. var tasks = list.Select(url => client.GetStringAsync(url)).ToArray();
  11. return await Task.WhenAll(tasks);
  12. }
  13. }

再举一个场景:同时调用多个同效果的API,有一个返回就好了,其他的忽略

  1. /// <summary>
  2. /// 2.返回首先完成的Task
  3. /// </summary>
  4. /// <param name="list"></param>
  5. /// <returns></returns>
  6. private static async Task<string> GetIPAsync(IEnumerable<string> list)
  7. {
  8. using (var client = new HttpClient())
  9. {
  10. var tasks = list.Select(url => client.GetStringAsync(url)).ToArray();
  11. var task = await Task.WhenAny(tasks); // 返回第一个完成的Task
  12. return await task;
  13. }
  14. }

一个async方法被await调用后,当它恢复运行时就会回到原来的上下文中运行。

如果你的Task不再需要上下文了可以使用:task.ConfigureAwait(false),eg:写个日记还要啥上下文?

逆天的建议是:在核心代码里面一种使用ConfigureAwait,用户页面相关代码,不需要上下文的加上

其实如果有太多await在上下文里恢复那也是比较卡的,使用ConfigureAwait之后,被暂停后会在线程池里面继续运行

再看一个场景:比如一个耗时操作,我需要指定它的超时时间:

  1. /// <summary>
  2. /// 3.超时取消
  3. /// </summary>
  4. /// <returns></returns>
  5. private static async Task<string> CancellMethod()
  6. {
  7. //实例化取消任务
  8. var cts = new CancellationTokenSource();
  9. cts.CancelAfter(TimeSpan.FromSeconds(3)); // 设置失效时间为3s
  10. try
  11. {
  12. return await DoSomethingAsync(cts.Token);
  13. }
  14. // 任务已经取消会引发TaskCanceledException
  15. catch (TaskCanceledException ex)
  16. {
  17.  
  18. return "false";
  19. }
  20. }
  21. /// <summary>
  22. /// 模仿一个耗时操作
  23. /// </summary>
  24. /// <returns></returns>
  25. private static async Task<string> DoSomethingAsync(CancellationToken token)
  26. {
  27. await Task.Delay(TimeSpan.FromSeconds(5), token);
  28. return "ok";
  29. }

异步这块简单回顾就不说了,留两个扩展,你们自行探讨:

  1. 进度方面的可以使用IProgress<T>,就当留个作业自己摸索下吧~
  2. 使用了异步之后尽量避免使用task.Wait or task.Result,这样可以避免死锁

Task其他新特征去官网看看吧,引入到此为止了。


2.并行编程(Parallel)

这个其实出来很久了,现在基本上都是用PLinq比较多点,主要就是:

  1. 数据并行:重点在处理数据(eg:聚合)
  2. 任务并行:重点在执行任务(每个任务块尽可能独立,越独立效率越高)

数据并行

以前都是Parallel.ForEach这么用,现在和Linq结合之后非常方便.AsParallel()就OK了

说很抽象看个简单案例:

  1. static void Main(string[] args)
  2. {
  3. IEnumerable<int> list = new List<int>() { 1, 2, 3, 4, 5, 7, 8, 9 };
  4. foreach (var item in ParallelMethod(list))
  5. {
  6. Console.WriteLine(item);
  7. }
  8. }
  9. /// <summary>
  10. /// 举个例子
  11. /// </summary>
  12. private static IEnumerable<int> ParallelMethod(IEnumerable<int> list)
  13. {
  14. return list.AsParallel().Select(x => x * x);
  15. }

正常执行的结果应该是:

  1. 1
  2. 4
  3. 9
  4. 25
  5. 64
  6. 16
  7. 49
  8. 81

并行之后就是这样了(不管顺序了):

  1. 25
  2. 64
  3. 1
  4. 9
  5. 49
  6. 81
  7. 4
  8. 16

当然了,如果你就是对顺序有要求可以使用:.AsOrdered()

  1. /// <summary>
  2. /// 举个例子
  3. /// </summary>
  4. private static IEnumerable<int> ParallelMethod(IEnumerable<int> list)
  5. {
  6. return list.AsParallel().AsOrdered().Select(x => x * x);
  7. }

其实实际项目中,使用并行的时候:任务时间适中,太长不适合,太短也不适合

记得大家在项目里经常会用到如SumCount等聚合函数,其实这时候使用并行就很合适

  1. var list = new List<long>();
  2. for (long i = 0; i < 1000000; i++)
  3. {
  4. list.Add(i);
  5. }
  6. Console.WriteLine(GetSumParallel(list));
  1. private static long GetSumParallel(IEnumerable<long> list)
  2. {
  3. return list.AsParallel().Sum();
  4. }

time dotnet PLINQ.dll

  1. 499999500000
  2. real 0m0.096s
  3. user 0m0.081s
  4. sys 0m0.025s

不使用并行:(稍微多了点,CPU越密集差距越大)

  1. 499999500000
  2. real 0m0.103s
  3. user 0m0.092s
  4. sys 0m0.021s

其实聚合有一个通用方法,可以支持复杂的聚合:(以上面sum为例)

  1. .Aggregate(
  2. seed:0,
  3. func:(sum,item)=>sum+item
  4. );

稍微扩展一下,PLinq也是支持取消的,.WithCancellation(CancellationToken)

Token的用法和上面一样,就不复述了,如果需要和异步结合,一个Task.Run就可以把并行任务交给线程池了

也可以使用Task的异步方法,设置超时时间,这样PLinq超时了也就终止了

PLinq这么方便,其实也是有一些小弊端的,比如它会直接最大程度的占用系统资源,可能会影响其他的任务,而传统的Parallel则会动态调整


任务并行(并行调用)

这个PLinq好像没有对应的方法,有新语法你可以说下,来举个例子:

  1. await Task.Run(() =>
  2. Parallel.Invoke(
  3. () => Task.Delay(TimeSpan.FromSeconds(3)),
  4. () => Task.Delay(TimeSpan.FromSeconds(2))
  5. ));

取消也支持:

  1. Parallel.Invoke(new ParallelOptions() { CancellationToken = token }, actions);

扩充说明

其实还有一些比如数据流响应编程没说,这个之前都是用第三方库,刚才看官网文档,好像已经支持了,所以就不卖弄了,感兴趣的可以去看看,其实项目里面有流数据相关的框架,eg:Spark,都是比较成熟的解决方案了基本上也不太使用这些了。

然后还有一些没说,比如NetCore里面不可变类型(列表、字典、集合、队列、栈、线程安全字典等等)以及限流任务调度等,这些关键词我提一下,也方便你去搜索自己学习拓展

先到这吧,其他的自己探索一下吧,最后贴一些Nuget库,你可以针对性的使用:

  1. 数据流Microsoft.Tpl.Dataflow
  2. 响应编程(Linq的Rx操作):Rx-Main
  3. 不可变类型Microsoft.Bcl.Immutable

不得不感慨一句,微软妈妈真的花了很多功夫,Net的并发编程比Python省心多了(完)

Python3 与 C# 并发编程之~ 进程篇的更多相关文章

  1. Python3 与 C# 并发编程之~进程先导篇

      在线预览:http://github.lesschina.com/python/base/concurrency/1.并发编程-进程先导篇.html Python3 与 C# 并发编程之- 进程篇 ...

  2. Python3 与 C# 并发编程之~ 协程篇

      3.协程篇¶ 去年微信公众号就陆陆续续发布了,我一直以为博客也汇总同步了,这几天有朋友说一直没找到,遂发现,的确是漏了,所以补上一篇 在线预览:https://github.lesschina.c ...

  3. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  4. python并发编程之进程、线程、协程的调度原理(六)

    进程.线程和协程的调度和运行原理总结. 系列文章 python并发编程之threading线程(一) python并发编程之multiprocessing进程(二) python并发编程之asynci ...

  5. Java并发编程:进程的创建

    Java并发编程:进程的创建 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} J ...

  6. Python3 与 C# 并发编程之~ 线程篇

      2.线程篇¶ 在线预览:https://github.lesschina.com/python/base/concurrency/3.并发编程-线程篇.html 示例代码:https://gith ...

  7. Python3 与 C# 并发编程之~ Net篇

    NetCore并发编程 示例代码:https://github.com/lotapp/BaseCode/tree/master/netcore/4_Concurrency 先简单说下概念(其实之前也有 ...

  8. Python 并发编程:PoolExecutor 篇

    个人笔记,如有疏漏,还请指正. 使用多线程(threading)和多进程(multiprocessing)完成常规的并发需求,在启动的时候 start.join 等步骤不能省,复杂的需要还要用 1-2 ...

  9. Java并发编程 | 从进程、线程到并发问题实例解决

    计划写几篇文章讲述下Java并发编程,帮助一些初学者成体系的理解并发编程并实际使用,而不只是碎片化的了解一些Synchronized.ReentrantLock等技术点.在讲述的过程中,也想融入一些相 ...

随机推荐

  1. SpringBoot数据库读写分离之基于Docker构建主从数据库同步实例

    看了好久的SpringBoot结合MyBatista实现读写,但是一直没有勇气实现他,今天终于接触到了读写分离的东西,读写分离就是讲读操作执行在Slave数据库(从数据库),写操作在Master数据库 ...

  2. 一个「学渣」从零开始的Web前端自学之路

    从 13 年专科毕业开始,一路跌跌撞撞走了很多弯路,做过餐厅服务员,进过工厂干过流水线,做过客服,干过电话销售可以说经历相当的“丰富”. 最后的机缘巧合下,走上了前端开发之路,作为一个非计算机专业且低 ...

  3. 测者的测试技术笔记:揭开java method的一个秘密--巨型函数

    相信,很多人都不知道Java的Method的上限为64K.本文将超过这个上限的函数叫做巨型函数. 巨型函数的问题 1.如果代码超过了这个限制,Java编译器就报"Code too large ...

  4. 【公众号系列】SAP的新零售

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[公众号系列]SAP的新零售   写在前面 还是 ...

  5. AngularJS学习之旅—AngularJS Scope作用域(五)

    1.AngularJS Scope(作用域) Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带. Scope 是一个对象,有可用的方法和属性. Sco ...

  6. 文件操作命令(rename)

    Rename 命令: // 描述: 重命名文件或目录. // 语法: rename [<Drive>:][<Path>]<FileName1> <FileNa ...

  7. scheme实现最基本的自然数下的运算

    版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖.如要转贴,必须注明原文网址 http://www.cnblogs.com/Colin-Cai/p/9123363.html 作者:窗户 Q ...

  8. Ajax 与文件上传

    一 Ajax篇 1 ajax简介(Asynchronous Javascript And XML) 异步,Js,XML,即使用Javascript语言与服务器进行异步交互,传输的数据为xml(可扩展标 ...

  9. loadrunner关联及web_reg_save_param方法浅析

    一.什么是关联 关联(correlation):脚本回放过程中,客户端发出请求,通过关联函数所定义的左右边界值(也就是关联规则),在服务器所响应的内容中查找,得到相应的值,已变量的形式替换录制时的静态 ...

  10. Go语言中定时器cron的基本使用

    安装:go get github.com/robfig/cron  如果出不去就用gopm 例子: package main import ( "fmt" "github ...