哈喽大家好,我是咸鱼
几天前,IBM 工程师 Martin Heinz 发文表示 python 3.12 版本回引入"Per-Interpreter GIL”,有了这个 Per-Interpreter 全局解释器锁,python 就能实现真正意义上的并行/并发
我们知道,python 的多线程/进程并不是真正意义上的多线程/进程,这是因为 python GIL (Global Interpreter Lock)导致的
而即将发布的 Python 3.12 中引入了名为 "Per-Interpreter GIL" 的新特性,能够实现真正的并发
接下来我们来看下这篇文章,原文链接如下:
http://martinheinz.dev/blog/97
Python 到现在已经 32 岁了,但它到现在还没有实现适当的、真正的并发/并行
由于将在 Python 3.12 (预计 2023 年 10 月发布)中引入 “Per-Interpreter GIL”(全局解释器锁),这种情况将会被改变
虽然距离 python 3.12 的发布还有几个月的时间,但是相关代码已经实现了。所以让我们提前来了解一下如何使用子解释器 API(ub-interpreters API) 来编写出真正的并发Python代码
我们首先来看下这个 “Per-Interpreter GIL” 是如何解决 Python 缺失适当并发性这个问题的
简单来讲,GIL(全局解释器锁)是一个互斥锁,它只允许一个线程控制 Python 解释器(某个线程想要执行,必须要先拿到 GIL ,在一个 python 解释器里面,GIL 只有一个,拿不到 GIL 的就不允许执行)
这就意味着即使你在 Python 中创建多个线程,也只会有一个线程在运行
随着 “Per-Interpreter GIL” 的引用,单个 python 解释器不再共享同一个 GIL。这种隔离级别允许每个子 python 解释器真正地并发运行
这意味着我们可以通过生成额外的子解释器来绕过 Python 的并发限制,其中每个子解释器都有自己的GIL(拿到一个 GIL 锁)
更详细的说明请参见 PEP 684,该文档描述了此功能/更改:http://peps.python.org/pep-0684/#per-interpreter-state
想要使用这个新功能,我们需要安装最新的 python 版本,这需要源码编译安装
# http://devguide.python.org/getting-started/setup-building/#unix-compiling
git clone http://github.com/python/cpython.git
cd cpython
./configure --enable-optimizations --prefix=$(pwd)/python-3.12
make -s -j2
./python
# Python 3.12.0a7+ (heads/main:22f3425c3d, May 10 2023, 12:52:07) [GCC 11.3.0] on linux
# Type "help", "copyright", "credits" or "license" for more information.
现在我们已经安装好了最新版本,那么我们该如何使用子解释器呢?我们可以直接通过 import
来导入吗?不幸的是,还不能
正如 PEP-684 中指出的: ...this is an advanced feature meant for a narrow set of users of the C-API.
Per-Interpreter GIL 的特性目前只能通过 C-API 使用,还没有直接的接口供开发人员使用
接口预计会在 PEP 554中出现,如果大家能够接受,它应该会在 Python 3.13 中出现,在这个版本出现之前,我们必须自己想办法来实现子解释器
虽然还没有相关文档,也没有相关模块可以导入,但 CPython 代码库中有一些代码段向我们展示了如何使用它:
_xxsubinterpreters
模块(因为是通过 C 实现的,所以命名比较奇怪,而且在 python 中不能够简单地去检查代码)# Choose one of these:
import _xxsubinterpreters as interpreters
from test.support import interpreters
通常情况下我们一般用上面的第二种方法来实现
我们已经找到了子解释器,但我们还需要通过 test 模块去借用一些辅助函数,以便将代码传递给子解释器,辅助函数如下
from textwrap import dedent
import os
# http://github.com/python/cpython/blob/
# 15665d896bae9c3d8b60bd7210ac1b7dc533b093/Lib/test/test__xxsubinterpreters.py#L75
def _captured_script(script):
r, w = os.pipe()
indented = script.replace('\n', '\n ')
wrapped = dedent(f"""
import contextlib
with open({w}, 'w', encoding="utf-8") as spipe:
with contextlib.redirect_stdout(spipe):
{indented}
""")
return wrapped, open(r, encoding="utf-8")
def _run_output(interp, request, channels=None):
script, rpipe = _captured_script(request)
with rpipe:
interp.run(script, channels=channels)
return rpipe.read()
将 interpreters
模块与上面的辅助函数组合在一起,便可以生成第一个子解释器:
from test.support import interpreters
main = interpreters.get_main()
print(f"Main interpreter ID: {main}")
# Main interpreter ID: Interpreter(id=0, isolated=None)
interp = interpreters.create()
print(f"Sub-interpreter: {interp}")
# Sub-interpreter: Interpreter(id=1, isolated=True)
# http://github.com/python/cpython/blob/
# 15665d896bae9c3d8b60bd7210ac1b7dc533b093/Lib/test/test__xxsubinterpreters.py#L236
code = dedent("""
from test.support import interpreters
cur = interpreters.get_current()
print(cur.id)
""")
out = _run_output(interp, code)
print(f"All Interpreters: {interpreters.list_all()}")
# All Interpreters: [Interpreter(id=0, isolated=None), Interpreter(id=1, isolated=None)]
print(f"Output: {out}") # Result of 'print(cur.id)'
# Output: 1
生成和运行新解释器的一种方法是使用 create()
函数,然后将解释器与我们想要执行的代码一起传递给 _run_output()
辅助函数
还有一种更简单的方法,如下所示
interp = interpreters.create()
interp.run(code)
直接使用 interpreters
模块的 run
方法。
但如果我们运行上面这两段代码时,会收到以下报错
Fatal Python error: PyInterpreterState_Delete: remaining subinterpreters
Python runtime state: finalizing (tstate=0x000055b5926bf398)
为了避免这个报错,我们还需要清理一些悬挂的解释器:
def cleanup_interpreters():
for i in interpreters.list_all():
if i.id == 0: # main
continue
try:
print(f"Cleaning up interpreter: {i}")
i.close()
except RuntimeError:
pass # already destroyed
cleanup_interpreters()
# Cleaning up interpreter: Interpreter(id=1, isolated=None)
# Cleaning up interpreter: Interpreter(id=2, isolated=None)
虽然使用上面的辅助函数运行代码是可行的,但在 threading
模块中使用熟悉的接口可能会更方便
import threading
def run_in_thread():
t = threading.Thread(target=interpreters.create)
print(t)
t.start()
print(t)
t.join()
print(t)
run_in_thread()
run_in_thread()
# <Thread(Thread-1 (create), initial)>
# <Thread(Thread-1 (create), started 139772371633728)>
# <Thread(Thread-1 (create), stopped 139772371633728)>
# <Thread(Thread-2 (create), initial)>
# <Thread(Thread-2 (create), started 139772371633728)>
# <Thread(Thread-2 (create), stopped 139772371633728)>
我们通过把 interpreters.create
函数传递给Thread
,它会自动在线程内部生成新的子解释器
我们也可以结合这两种方法,并将辅助函数传递给 threading.Thread
:
import time
def run_in_thread():
interp = interpreters.create(isolated=True)
t = threading.Thread(target=_run_output, args=(interp, dedent("""
import _xxsubinterpreters as _interpreters
cur = _interpreters.get_current()
import time
time.sleep(2)
# Can't print from here, won't bubble-up to main interpreter
assert isinstance(cur, _interpreters.InterpreterID)
""")))
print(f"Created Thread: {t}")
t.start()
return t
t1 = run_in_thread()
print(f"First running Thread: {t1}")
t2 = run_in_thread()
print(f"Second running Thread: {t2}")
time.sleep(4) # Need to sleep to give Threads time to complete
cleanup_interpreters()
上面的代码中演示了如何使用 _xxsubinterpreters
模块来实现 (方法一)
我们还在每个线程中休眠 2 秒来模拟“工作”状态
请注意,我们甚至不必调用 join() 函数等待线程完成,只需在线程完成时清理解释器即可
如果我们进一步挖掘 CPython test
模块,我们还会发现 RecvChannel 和 SendChannel 类的实现类似于 Golang 中已知的通道
# http://github.com/python/cpython/blob/
# 15665d896bae9c3d8b60bd7210ac1b7dc533b093/Lib/test/test_interpreters.py#L583
r, s = interpreters.create_channel()
print(f"Channel: {r}, {s}")
# Channel: RecvChannel(id=0), SendChannel(id=0)
orig = b'spam'
s.send_nowait(orig)
obj = r.recv()
print(f"Received: {obj}")
# Received: b'spam'
cleanup_interpreters()
# Need clean up, otherwise:
# free(): invalid pointer
# Aborted (core dumped)
上面的例子介绍了如何创建一个接收端通道(r)和发送端通道(s),然后我们使用 send_nowait
方法将数据发送,通过 recv
方法来接收数据
这个通道实际上只是另一个解释器,和以前一样,我们需要在处理完它之后进行清理
如果我们想要修改或者调整子解释器的选项(这些选项通常在 C 代码中设置),我们可以使用
test.support
模块中的代码,具体来说是run_in_subinterp_with_config
import test.support
def run_in_thread(script):
test.support.run_in_subinterp_with_config(
script,
use_main_obmalloc=True,
allow_fork=True,
allow_exec=True,
allow_threads=True,
allow_daemon_threads=False,
check_multi_interp_extensions=False,
own_gil=True,
)
code = dedent(f"""
from test.support import interpreters
cur = interpreters.get_current()
print(cur)
""")
run_in_thread(code)
# Interpreter(id=7, isolated=None)
run_in_thread(code)
# Interpreter(id=8, isolated=None)
上面这个run_in_subinterp_with_config
函数是 C 函数的 Python API。它提供了一些子解释器选项,如 own_gil
,指定子解释器是否应该拥有自己的 GIL
根据Databreachtoday上周报道,知名3D模型网站Thingiverse泄露了22.8万名用户资料,但相关人员最近发现,这起泄露事件还可能导致约5万台打印机被劫持。从2020年10月起,共有36GB大小的Thingiverse数据被泄露,包括用户的电子邮件地址、IP地址、用户名、居住地址等信息。曾在Thingiverse母公司MakerBot工作过的软件工程师TJHorner表示,此次泄露的数据中包括一些OAuth令牌,这些令牌可用于远程访问MakerBot第5代甚至更高版本的3D打印机,并调用打印机上的摄像头对其实行监视。Horner使用泄露的OAuth令牌监视自己的MakerBotMETHODX打印机Horner认为,如果打印机连接到互联网,任何拥有这些令牌的人都可以完全控制打印机,攻击者可能会向3D打印机发送错误的示意图,并损坏打印机的步进电机,此外,这些泄露的令牌还能让攻击者访问Thingiverse用户的账户信息。Horner列出了受影响的打印机机型,包括Replicator5thGen、ReplicatorMini、ReplicatorZ18、Replicator
前言本文收录于专辑:http://dwz.win/HjK,点击解锁更多数据结构与算法的知识。你好,我是彤哥,一个每天爬二十六层楼还不忘读源码的硬核男人。相信大家都有过抢票、刷票的经验,每年年底,这都是一场盛宴。然而,你有没有想过12306的抢票算法是怎么实现的呢?没有吧,想过,还是没有头绪?今天,我们就来曝光让人又爱又恨的12306是如何实现抢票的。位运算回顾我们知道计算机只能识别0和1,要操作这些0和1,只能通过位运算来进行,那么,一共有几种位运算呢?让我们来回顾一下:运算符号举例结果与&1101&01100100或|1101&01101111异或^1101^01101011取反~11010010左移<<1101<<111010带符号右移>>1101>>11110不带符号右移>>>1101>>>10110以上位运算以Java为例,其他语言中可能没有>>>操作。OK,位运算的简单回顾就到这里,还有不懂的同学可以自行百度一下。位图虽然大部分语言都有提供位运算,但是
布尔类型python中True表示真,False表示假,它们是布尔类型:>>>type(True) <class'bool'>复制在python中,bool的True和False是数值1和0的字符串表示格式,实际上bool类型是int类型的一个子类。>>>bool.__bases__ (<class'int'>,)复制因为True/False是数值1和0的另一种表示方式,它们可以直接参与数值运算。>>>True+2 3 >>>False+2-1 1复制True/False的各种形式虽然True代表1,False代表0。但实际上,python中的任何一个数据对象要么是True,要么是False,所以可以直接在布尔测试的表达式中使用,而并非一定要去大小比较、通过函数测试等等。比如:if"a": while1:复制可以通过bool()函数来测试数据对象、表达式是True还是False。例如:>>>bool(0) Fals
目录 1资源加载1.1处理img.src这样的资源属性1.2在内嵌样式中background-image如何加载1.3在样式块中background-image如何加载2与资源加载有关的两个加载器2.1url-loader2.2file-loader3关于如何选择优先在哪里配置的问题源码1资源加载1.1处理img.src这样的资源属性当VueLoader编译单文件组件中的<template>块时,它也会将所有遇到的资源URL转换为webpack模块请求。例如,下面的模板代码片段:<imgsrc="../image.png"> 复制将会被编译成为:createElement('img',{ attrs:{ src:require('../image.png') } }) 复制require('../image.png')这是一个模块的请求了。除了img.src还有哪些标签的属性,会自动转为模块请求,这是由VueLoader模块的transformAssetUrls定义的。transfo
本文介绍SQL和关系代数的起源,没有干货,请谨慎阅读。如何向你奶奶解释SQL和NoSQL 最近Medium上出现了一个面试题:如何向你奶奶解释SQL和NoSQL的区别。我看作者是用自己的结构化的家族谱来比喻sql和nosql的区别的,写的挺好就是有点啰嗦,面试官可没时间听你在那滔滔不绝。我个人觉得,sql和nosql的区别海了去了,一两句话可概括不完,更何况是讲给你奶奶听,所以如果真被问到这个问题,可以挑选其中一个最主要的区别来类比就好,比如sql是通用的,nosql是专用的。所以我的答案是这样的: 尊敬的祖母,数据库就是用来存储数据的仓库,就像我们生活中存放物品的容器一样,但是容器也有通用和专用之分,比如塑料袋就是通用容器,因为塑料袋可以装各种东西:即可以装糖果,也可以装铅笔。但我们也可以选择一种更合适的存储方式:用糖果盒来装糖果,用笔筒来装铅笔。糖果盒和笔筒就是专用容器。数据和物品一样,也可以采用通用或者专用的存储方式,各有利弊,SQL就是通用数据库,NoSQL就是专用数据库,这就是他俩的区别。 集合论与关系代数可是为什么SQL可以做到通用呢?世界上的数据结构千千万,为啥SQL可以
dp思路 dp方程的键为两个柱子之间的高度差,值为当前高度差情况下,两个柱子的最小高度 状态转移的时候有三种情况,其中后两种可以合并 最后dp[0]保存的就是两个柱子高度差为0的时候,两个柱子的最小高度codefunctallestBillboard(rods[]int)int{ sum:=0 for_,v:=rangerods{ sum+=v } dp:=make([]int,sum+1) fork,_:=rangedp{ dp[k]=-1 } dp[0]=0 for_,v:=rangerods{ cur:=make([]int,sum+1) copy(cur,dp) //fori:=0;i<=sum;i++{ ford,val:=rangecur{ ifval!=-1{ ifd+v<=sum{ dp[d+v]=mymax(dp[d+v],val) } dp[myabs(d-v)]=mymax(dp[myabs(d-v)],val+mymin(v,d)) } } //fmt.Pr
DFINITY是什么?一篇简短文章带你认识硅谷最火的DFINITY—以太坊的最强竞争对手!一、DFINITY是什么?DFINITY宣传片DFINITY是无限扩容的智能分布式云计算系统和第三代区块链,并且高度兼容以太坊现有应用。DFINITY团队包括了世界领先的密码学家,数学家和资深工程师等,如斯坦福BLS技术的发明人,bitcoinasicboost和p2contract的发明人,来自耶鲁大学和欧洲EPFL分布式计算中心的多方计算和密码学研究者等。Dfinity可以被认为是下一个计算机——它是一个巨大的分布式计算机。每个需要运行计算的人,不是在自己的计算机上运行,而是在Dfinity网络上运行。Dfinity和亚马逊AWS的不同之处在于,AWS拥有一个公司内部所有计算能力,而Dfinity是云计算的一个去中心化的版本。所以它是分散的,没有人拥有网络的所有节点。二、创立背景:Dfinity是由DominicWilliams创立,他也是DFINITY的首席科学家。他还有一些其他工作和Dfinity是并行并且达到共识的。相比亚马逊AWS,他需要找到更去中心化,比以太坊具有更多可扩展性的技术,
文|孟永辉 著名投资人朱啸虎有关摩拜单车与ofo若想盈利必先合并的言论再次引发人们关注。有关摩拜单车与ofo能否合并的讨论开始出现,整个市场再次将目光聚焦在了这两大共享单车巨头的身上。人们之所以会对这个问题如此关注,除了有滴滴与优步合并的前车之鉴之外,更多的是在关注共享单车市场。毋庸置疑,摩拜单车和ofo是处于共享单车"食物链"顶端的存在,如果两者合并将会对整个共享单车市场,甚至对整个共享经济市场都会产生影响。前期资本的轮番加注以及以阿里、腾讯为代表的互联网巨头的参与都不断强化着两者之间的影响力。另外,前段时间马化腾与朱啸虎互怼事件同样让这一次有关合并的传言多少有些突兀。或许因为滴滴与快的,滴滴与优步的合并让人有些猝不及防,所以人们才会对摩拜单车与ofo的合并充满期待,或许两者在市场当中的份额太大,以至于我们不得不关注。诚然,摩拜单车与ofo是否合并对于你我的影响都并不大,因为这是资本收割的必然结果。如果两个一味地缠斗而没有"眉来眼去"反倒有些不正常了。那么,摩拜单车与ofo会不会合并呢?它们之间合并的概率究竟有多大呢?共享单车行业深度调整,盈利
目录一、什么是Windows服务?二、创建Windows服务与安装/卸载批处理。 三、调试Windows服务。 正文 一、什么是Windows服务?答:MicrosoftWindows服务(即,以前的NT服务)使您能够创建在它们自己的Windows会话中可长时间运行的可执行应用程序。这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面。这使服务非常适合在服务器上使用,或任何时候,为了不影响在同一台计算机上工作的其他用户,需要长时间运行功能时使用。还可以在不同于登录用户的特定用户帐户或默认计算机帐户的安全上下文中运行服务。二、创建Windows服务。 打开:VisualStudio2010=>新建=>项目=>Windows服务,如图: 点击确定=>生成的Service1.cs文件视图上右键=>添加安装程序=>生成的ProjectInstaller.cs视图页面设置serviceInstaller1里的启动方式、Windows服务名称,如图:然后在设置serviceProcessInstaller1的账号信息,选择“本地系统”,如
Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie名称和值可以由服务器端开发自己定义,对于JSP而言也可以直接写入JSESSIONID用于标记一个会话(session),这样服务器可以知道该用户是否合法用户以及是否需要重新登录等,服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。Cookie是客户端技术,而HttpSession是服务器端技术。java中Cookie详细介绍:1、Cookie是什么? 一个小信息,由服务器写给浏览器的。由浏览器来保存。 客户端保存的Cookie信息,可以再次带给服务器。 Cookie类:javax.servlet.http.Cookie2、Cookie的属性:name:必须的value:必须的comment:可选的。注释path: 可选的,如果不设置路径,那么只有设置该cookie的URI及其子路径可以访问 写Cook
虽然很早就开始接触和使用Linux系列的系统,但是一直都是停留在使用层面,只能作为一个User,而并不是一个Developer,而要真正掌握Linux系列的系统,并以其作为开发环境进行开发,有很多知识是必须要了解的,就比如用户及用户权限管理。 用户和用户组文件在linux中,用户帐号,用户密码,用户组信息和用户组密码均是存放在不同的配置文件中的。在linux系统中,所创建的用户帐号和其相关信息(密码除外)均是存放在/etc/passwd配置文件中。由于所有用户对passwd文件均有读取的权限,因此密码信息并未保存在该文件中,而是保存在了/etc/shadow的配置文件中。在passwd文件中,一行定义一个用户帐号,每行均由多个不同的字段构成,各字段值间用:分隔,每个字段均代表该帐号某方面的信息。在刚安装完成的linux系统中,passwd配置文件已有很多帐号信息了,这些帐号是由系统自动创建的,他们是linux进程或部分服务程序正常工作所需要使用的账户,这些账户的最后一个字段的值一般为/sbin/nologin,表示该帐号不能用来登录linux系统。另外,若要使某个用户账户不能登录lin
1.前言最近pm临时提出了多种邮件验证操作的需求,因为一时间也没有找到好的邮件收发组件,也抱着研究ABP的心态,就花了几小时时间探究了一下ABP中关于Email的处理和操作。其实邮件操作大多大同小异,这次只是希望介绍一下ABP中实现功能的代码结构而已,以下是具体过程演示的ABP代码版本为0.9.0.0,不过后面版本对于这部分的修改较少,所以完全不影响之后版本的移植使用 2.实现过程ABP的Mail操作放在了Abp.Net.Mail和Abp.Net.Mail.Smtp中,第一步先让我们直接看看这个文件夹下类及接口的代码图(未经允许不可使用)1.代码图(重)根据代码图可以发现ABP对于Mail处理主要由三部分组成第一部分是通过继承SettingProvider的EmailSettingProvider来对Mail相关参数进行设置(其中EmailSettingNames定义相关字符串)第二部分是以IEmailSenderConfiguration接口为基派生出的对SettingProvider设置的邮件参数进行读取和传输的相关操作类第三部分是以IEmailSender接口为基派生出的Mail
来自读者投稿: 我在一家做微信营销的公司干技术leader,带40多个人,公司名就不说了。在这个位置上做了好几年,把团队从小带大,公司虽然不算风口浪尖上的高增长业务,但技术这块儿也从来没出过什么问题,我还是蛮自豪的。带团队时间久了,就能发现整个Team都渐渐疲了。前两年老板还专门买了个系统搞OKR,现在也不大提了;Scrum我们也搞了,用起来也就那样;项目管理工具试了好几个,禅道、Worktile、现在用Coding,反正有一个能用的就行;微服务化改造从去年开始在吭哧吭哧搞,我们自己搞得觉得很厉害,但业务部门那边就觉得没啥差别,搞不懂你们研发部门每天在弄些什么,赶紧做我们提的需求要紧。时间一久,很多原本不是问题的问题也会浮现出来。业务越做越复杂,一层层往上叠,迭代速度就越来越慢,感觉啥都没干呢一个Q唰的就过去了。有核心老员工离职,很多东西只有他熟,他一两天就能做出来的东西,新补上的人一两周也弄不出来。新员工进来也很懵逼,那么多业务那么多接口,学上一年也未必学得清楚。我也三十多了,就感觉自己懂的招儿都用得差不多了。毕竟自己早就离开一线,多年不写代码了,真的要我去给他们解决那些代码层面的问
已知函数Fun5的部分代码如下所示,其功能是:统计指针变量a所指的字符串中单词的个数,并存放在指针变量b所指的变量中。其中单词之间用空格分隔开。例如,若a所指的字符串为“Iloveyou”,则b所指的变量的值应为3,请在空格处补充程序。#include<stdio.h> intmain(){ voidfun(char*a,int*b); char*a="Iloveyou"; intnum=0; fun(a,&num); printf("%d",num); return0; } voidfun(char*a,int*b){ inti,num=0,word=0; charc; for(i=0;(c=*(a+i))!='\0';i++){ if(c=='')word=0; elseif(word==0){ word=1; num++; } } *b=num; }复制
一种函数:判断字符ch是否为英文字母,若为小写字母,返回2,若为大写字母,返回1。若不是字母,返回0。在标准c中相当于使用“isupper(ch)||islower(ch)”做测试,头文件加入或者(C语言使用)isupper原型:externintisupper(intc);头文件:(旧版本的编译器使用<ctype.h>)功能:判断字符c是否为大写英文字母说明:当参数c为大写英文字母(A-Z)时,返回非零值,否则返回零。附加说明:此为宏定义,非真正函数。islowerislower(测试字符是否为小写字母)相关函数isalpha,isupper表头文件#include(旧版本的编译器使用<ctype.h>)定义函数intislower(intc)函数说明检查参数c是否为小写英文字母。返回值若参数c为小写英文字母,则返回TRUE,否则返回NULL(0)。附加说明:此为宏定义,非真正函数。}#include#includeintmain(void){ charch;inttotal;total=0;//赋值/*统计字母块*/do{ ch=getchar();if(i
整合vite2+electron12跨平台仿抖音电脑版实战Vite2-ElectronDouYin。 基于vite2.0+electron12+vant3+swiper6+v3popup等技术跨端仿制抖音短视频+聊天+直播桌面版应用。实现短视频上下拖拽滑动、评论/聊天、执行dll截图、最小至托盘。支持键盘上下键切换、新开多窗口等功能。 一、使用技术 编辑器:VScode 框架技术:vite2.1.0+electron12.0.1+vuex4+vue-router@4 组件库:vant^3.0.10(有赞移动端vue3组件库) 打包工具:vue-cli-plugin-electron-builder 轮播组件:swiper^6.5.0 弹层组件:v3popup(基于vue3自定义移动端弹出框) 字体图标:阿里iconfont矢量图标库 二、项目结构 三、运行效果 ◆electron12实现顶部导航栏窗体拖拽+底部Tabbar 如上图:自定义无边框 frame:false 
安装 下载 redis官网地址:https://redis.io/ centos安装 创建软件放置目录mkdirsoft 进入soft目录并下载redis安装包。 cdsoft wgethttps://download.redis.io/releases/redis-6.0.9.tar.gz 复制 解压tar-zxvfredis-6.0.9.tar.gz 进入解压后目录,并查看README文件。了解软件说明和使用方式。 #阅读README,查看软件说明。可以得知在根目录下执行make命令 viREADME 复制 执行make命令进行编译。正确编译后可在src目录下看到运行redis的方法 创建服务,便于日后进行管理 makePREFIX=/opt/endea/redisinstall #设置环境变量 vi/etc/profile REDIS_HOME=/opt/endea/redis exportPATH=$PATH:$REDIS_HOME/bin source/etc/profile cd/utils ./install_server.sh #一步一步安装,选择端口
1可以自行抛出异常吗?什么时候需要?可以,例如在处理异常后,将异常抛出,让上一层异常处理块捕捉。 2如何设置异常?在方法体设置了抛出的异常,声明中也必须抛出吗?如何添加?方法添加了throws后,测试的代码在调用方法时是否必须处理异常? publicvoidsetAge(intage){ /* *当传入的参数不满足业务逻辑要求 *比如年龄不在0-100之间的数字认为不符合要求时, *可以当做异常抛出给调用者 * *whenthepostedparamsnotsetisfythe */ if(age<0||age>100){ thrownewRuntimeException("年龄不合法"); } this.age=age; } 必须,除了RuntimeException可以不加。 publicvoidsetAge(intage)throwsException{ if(age<0||age>100){ thrownewException("年龄不合法"); } this.age=age; } publicstaticv
最近phonegap已发布4.0的了。。速度提升了不少,很给力。小白们可以看下如何构建phonegap开发平台.此文将说明如何建立一个可以被vs2015打开的phonegap的项目。我还会加上ionic库和模板的操作说明:本文出自http://www.cnblogs.com/jacle169复制 1.下载phonegap桌面工具,http://phonegap.com/blog/2014/12/11/phonegap-desktop-app-beta/ 2.运行后直接建立新项目 3.填写项目参数: 4.点createproject后 5.此时你已经可以通过浏览器输入http://127.0.0.1:3000测试运行效果了。 6.安装ionic框架,首先你的电脑必须安装node.js,下载地址:http://nodejs.org/download/ 7.打开cmd窗:$npminstall-gcordovaionic复制8.更新ionic框架到刚刚建立的项目里,先连接到项目的上一级目录:复制$cdD:\coolpi复制9.提供了三种不同的ionic模板,请自行选择,请到官网自行查
题目描述说一大堆要求,看着吓人,但是好像都和最小生成树的性质直接就套上了,也就是说,一个模板加个变量统计下就没了。 1#include<algorithm> 2#include<iostream> 3#include<vector> 4#include<cstdio> 5usingnamespacestd; 6constintN=16384,INF=0x3f3f3f3f; 7structnode{ 8intx,y,v; 9booloperator<(constnodeoth)const{returnv<oth.v;} 10}; 11intn,m,u,v,res,cnt,fth[N]; 12vector<node>e; 13intfnd(intx){returnx==fth[x]?x:fth[x]=fnd(fth[x]);} 14intmain(){ 15for(inti=0;i<N;i++)fth[i]=i; 16cin>>n>>m; 17for(inti=0;i<m;i