• [常见FAQ] 为什么本地编译正常,提交上去显示编译错误?
    已经参考过文档里的解决办法了,有没有遇到过同样问题的盆友,求解决办法,感谢(是C++ 用vs写的)
  • [问题求助] 检查许可证失败
    许可证正常导入,设计器运行脚本提示检查许可证失败,请检查本地许可文件或与Studio/助手的连接是否正常建立并确保许可未过期请问是什么原因?
  • [技术干货] Handle-转载
    1.Handler的实现原理从四个方面看 Handler 、 Message 、 MessageQueue 和 LooperHandler: 负责消息的发送和处理Message: 消息对象,类似于链表的一个结点 ;MessageQueue: 消息队列,用于存放消息对象的数据结构 ;Looper: 消息队列的处理者(用于轮询消息队列的消息对象 )Handler 发送消息时调用 MessageQueue 的 enqueueMessage 插入一条信息到 MessageQueue,Looper 不断轮询调用MeaasgaQueue 的 next 方法 如果发现 message 就调用 handler 的 dispatchMessage , dispatchMessage 被成功调 用,接着调用handlerMessage() 。2.子线程中能不能直接new一个Handler,为什么主线程可以 主线程的Looper第一次调用loop方法,什么时候,哪个类        不能,因为Handler 的构造方法中,会通过 Looper.myLooper() 获取 looper 对象,如果为空,则抛出异常, 主线程则因为已在入口处ActivityThread 的 main 方法中通过 Looper.prepareMainLooper() 获取到这个对象, 并通过 Looper.loop() 开启循环,在子线程中若要使用 handler ,可先通过 Loop.prepare 获取到 looper 对象,并使用 Looper.loop()开启循环。3.Handler导致的内存泄露原因及其解决方案1.Java 中非静态内部类和匿名内部类都会隐式持有当前类的外部引用2. 我们在 Activity 中使用非静态内部类初始化了一个 Handler, 此 Handler 就会持有当前 Activity 的引用。3. 我们想要一个对象被回收,那么前提它不被任何其它对象持有引用,所以当我们 Activity 页面关闭之后 , 存在引用关 系:" 未被处理 / 正处理的消息 -> Handler 实例 -> 外部类 ", 如果在 Handler 消息队列 还有未处理的消息 / 正在处理消 息时 导致Activity 不会被回收,从而造成内存泄漏解决方案 :1. 将 Handler 的子类设置成 静态内部类 , 使用 WeakReference弱引用持有 Activity 实例2. 当外部类结束生命周期时,清空 Handler 内消息队列4.一个线程可以有几个Handler,几个Looper,几个MessageQueue对象        一个线程可以有多个Handler, 只有一个 Looper 对象 , 只有一个 MessageQueue 对象。 Looper.prepare() 函数中知道,。在Looper 的 prepare 方法中创建了 Looper 对象,并放入到 ThreadLocal 中,并通过 ThreadLocal 来获取 looper 的对象, ThreadLocal 的内部维护了一个 ThreadLocalMap 类 ,ThreadLocalMap 是以当前 thread 做为 key 的 , 因此可以得 知,一个线程最多只能有一个Looper 对象, 在 Looper 的构造方法中创建了 MessageQueue 对象,并赋值给 mQueue字段。因为 Looper 对象只有一个,那么 Messagequeue 对象肯定只有一个。5.Message对象创建的方式有哪些 & 区别,Message.obtain()怎么维护消息池的1.Message msg = new Message();每次需要 Message 对象的时候都创建一个新的对象,每次都要去堆内存开辟对象存储空间2.Message msg = Message.obtain();obtainMessage 能避免重复 Message 创建对象。它先判断消息池是不是为空,如果非空的话就从消息池表头的 Message取走 , 再把表头指向 next 。如果消息池为空的话说明还没有 Message 被放进去,那么就 new 出来一个 Message 对象。消息池使用 Message 链表 结构实现,消息池默认最大值 50 。消息在 loop 中被 handler 分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表的表头。3.Message msg = handler.obtainMessage(); 其内部也是调用的 obtain() 方法6.Handler 有哪些发送消息的方法sendMessage(Message msg)sendMessageDelayed(Message msg, long uptimeMillis)post(Runnable r)postDelayed(Runnable r, long uptimeMillis)sendMessageAtTime(Message msg,long when)7.Handler的post与sendMessage的区别和应用场景1. 源码sendMessagesendMessage-sendMessageAtTime-enqueueMessage 。postsendMessage-getPostMessage-sendMessageAtTime-enqueueMessage getPostMessage 会先生成一个 Messgae,并且把 runnable 赋值给 message 的 callback2.Looper->dispatchMessage 处理时public void dispatchMessage(@NonNull Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}dispatchMessage 方法中直接执行 post 中的 runnable 方法。而 sendMessage 中如果 mCallback 不为 null 就会调用 mCallback.handleMessage(msg) 方法,如果 handler 内的 callback不为空,执行 mCallback.handleMessage(msg) 这个处理消息并判断返回是否为 true ,如果返回 true ,消息 处理结束,如果返回false,handleMessage(msg) 处理。否则会直接调用 handleMessage 方法。post方法和 handleMessage 方法的不同在于,区别就是调用 post 方法的消息是在 post 传递的 Runnable 对象的 run 方法中处理,而调用sendMessage 方法需要重写 handleMessage 方法或者给 handler 设置 callback ,在 callback 的handleMessage 中处理并返回 true应用场景post 一般用于单个场景 比如单一的倒计时弹框功能 sendMessage 的回调需要去实现 handleMessage Message 则做为参数 用于多判断条件的场景。8.handler postDealy后消息队列有什么变化,假设先 postDelay 10s, 再 postDelay 1s, 怎么处理这2条消息sendMessageDelayed-sendMessageAtTime-sendMessage————————————————版权声明:本文为CSDN博主「界斗士」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_14931305/article/details/126245284
  • [技术干货] 多线程进阶:常见数据结构的安全性分析-转载
    一、常见数据结构非线程安全的数据结构:ArrayList,LinkedList,ArrayQueue,HashMap,HashSet线程安全的数据结构:Vector,Stack,Hashtable,CopyOnWriteArrayList,ConcurrentHashMap二、ArrayList2-1 线程不安全的原因看源码public boolean add(E e) {    ensureCapacityInternal(size + 1);  // Increments modCount!!    // 该方法是容量保障,当容纳不下新增的元素时会进行扩容    elementData[size++] = e;    return true;}分析:当数组长度为10,而size = 9时,此时A线程判断可以容纳,B线程也来判断发现可以容纳(这是因为add非原子操作)。当A添加完之后,B线程再添加的话,就会报错(数组越界异常)而且这一步elementData[size++] = e也非原子性的.可以拆分为elementData[size] = e 和 size ++;在多线程的情况下很容易出现elementData[size] = e1; elementData[size] = e2; size++; size++; 的情况2-2 Vector实现安全Vector的add()源码:    public synchronized void addElement(E obj) {        modCount++;        ensureCapacityHelper(elementCount + 1);        elementData[elementCount++] = obj;    }分析: Vector的add方法加了synchronized ,而ArrayList没有,所以ArrayList线程不安全,但是,由于Vector加了synchronized ,变成了串行,所以效率低。回到目录…三、CopyOnWriteArrayListCopyOnWrite容器即写时复制的容器。// java.util.concurrent包下List<String> list = new CopyOnWriteArrayList<String>();123-1 如何实现线程安全? 通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。 这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。3-2 特征CopyOnWriteArrayList(写数组的拷贝)是ArrayList的一个线程安全的变体,CopyOnWriteArrayList和CopyOnWriteSet都是线程安全的集合,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。它绝对不会抛出ConcurrentModificationException的异常。因为该列表(CopyOnWriteArrayList)在遍历时将不会被做任何的修改。CopyOnWriteArrayList适合用在“读多,写少”的并发场景中,比如缓存、白名单、黑名单。它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作要加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为开始读的那一刻已经确定了读的对象是旧对象。3-3 缺点在写操作时,因为复制机制,会导致内存占用过大。不能保证实时性的数据一致,“脏读”。回到目录…四、HashMap4-1 底层原理不清楚的小白看看之前两篇文章,就可以很容易搞懂HashMap的底层实现原理了。 Java数据结构之哈希表 JDK中的Set和Map解析4-2 线程不安全的原因单看 HashMap 中的 put 操作:JDK1.7头插法 –> 将链表变成环 –> 死循环JDK1.8尾插法 –> 数据覆盖回到目录…五、ConcurrentHashMap// java.util.concurrent包下Map<Integer, String> map = new ConcurrentHashMap<>();125-1 实现原理JDK1.7时,采用分段锁JDK1.8时,只针对同一链表内互斥,不是同一链表内的操作就不需要互斥。但是一旦遇到需要扩容的时候,涉及到所有链表,此时就不是简单的互斥了。扩容的过程:当A线程put 操作时发现需要扩容,则它自己创建一个扩容后的新数组。A线程只把当前桶中的节点重新计算哈希值放入新数组中,并且标记该桶元素已经迁移完成。由于其它桶中的元素还没有迁移,所以暂时还不删除旧数组。等其它线程抢到锁并在桶内做完操作时,需要义务的将该桶节点全部搬移并标记桶。直到最后一个线程将最后一桶节点搬移完毕,则它需要把旧数组删除。5-2 与Hashtable的区别HashTable和HashMap的实现原理几乎一样,差别在于:HashTable不允许key和value为null;HashTable是线程安全的。 但是HashTable线程安全的策略实现代价却比较大,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把全表锁。当一个线程访问或操作该对象,那其他线程只能阻塞。 所以说,Hashtable 的效率低的离谱,几近废弃。————————————————版权声明:本文为CSDN博主「一只咸鱼。。」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq15035899256/article/details/125961682
  • [问题求助] Hyper-tuner_2.5.RC1_linux 任务采集失败
    【功能模块】在鲲鹏openlab , openEuler 20.03 (LTS-SP1)上安装Hyper-tuner_2.5.RC1【操作步骤&问题现象】1、新建任务-进程/线程性能分析, 采样类型:系统性能,进程性能,2、任务状态:任务采集失败,pidstat命令执行失败,请确认pidstat命令能否正常执行。3、后台可执行pidstat【截图信息】【日志信息】(可选,上传日志内容或者附件)2022-08-06 04:17:57 Sat|509023:281460682279808|process_sampling.py:137|sampling_task|CRITICAL|start the collect task2022-08-06 04:18:57 Sat|509023:281460682279808|process_sampling.py:147|sampling_task|WARNING|[ERROR] SysPerf.SysProcess.Collect.SampleStartError
  • [问题求助] 【weautomate】【安装启动后在桌面上没有界面,任务管理器中也没有weautomate 进程,但是在详情信息中显示】
    系统 :win 10已安装 vc【功能模块】安装问题【操作步骤&问题现象】1、安装weautomate 后启动软件,【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [技术干货] Python利用contextvars实现管理上下文变量【转】
    目录web 框架中的 requestThreadLocalcontextvarsc.Tokencontextvars.Context小结Python 在 3.7 的时候引入了一个模块:contextvars,从名字上很容易看出它指的是上下文变量(Context Variables),所以在介绍 contextvars 之前我们需要先了解一下什么是上下文(Context)。Context 是一个包含了相关信息内容的对象,举个例子:"比如一部 13 集的动漫,你直接点进第八集,看到女主角在男主角面前流泪了"。相信此时你是不知道为什么女主角会流泪的,因为你没有看前面几集的内容,缺失了相关的上下文信息。所以 Context 并不是什么神奇的东西,它的作用就是携带一些指定的信息。web 框架中的 request我们以 fastapi 和 sanic 为例,看看当一个请求过来的时候,它们是如何解析的。1234567891011121314151617181920212223242526272829303132# fastapifrom fastapi import FastAPI, Requestimport uvicorn app = FastAPI()  @app.get("/index")async def index(request: Request):    name = request.query_params.get("name")    return {"name": name}  uvicorn.run("__main__:app", host="127.0.0.1", port=5555) # ------------------------------------------------------- # sanicfrom sanic import Sanicfrom sanic.request import Requestfrom sanic import response app = Sanic("sanic")  @app.get("/index")async def index(request: Request):    name = request.args.get("name")    return response.json({"name": name})  app.run(host="127.0.0.1", port=6666)发请求测试一下,看看结果是否正确。可以看到请求都是成功的,并且对于 fastapi 和 sanic 而言,其 request 和 视图函数是绑定在一起的。也就是在请求到来的时候,会被封装成一个 Request 对象、然后传递到视图函数中。但对于 flask 而言则不是这样子的,我们看一下 flask 是如何接收请求参数的。123456789101112from flask import Flask, request app = Flask("flask")  @app.route("/index")def index():    name = request.args.get("name")    return {"name": name}  app.run(host="127.0.0.1", port=7777)我们看到对于 flask 而言则是通过 import request 的方式,如果不需要的话就不用 import,当然我这里并不是在比较哪种方式好,主要是为了引出我们今天的主题。首先对于 flask 而言,如果我再定义一个视图函数的话,那么获取请求参数依旧是相同的方式,但是这样问题就来了,不同的视图函数内部使用同一个 request,难道不会发生冲突吗?显然根据我们使用 flask 的经验来说,答案是不会的,至于原因就是 ThreadLocal。ThreadLocalThreadLocal,从名字上看可以得出它肯定是和线程相关的。没错,它专门用来创建局部变量,并且创建的局部变量是和线程绑定的。1234567891011121314151617181920212223242526272829import threading # 创建一个 local 对象local = threading.local() def get():    name = threading.current_thread().name    # 获取绑定在 local 上的 value    value = local.value    print(f"线程: {name}, value: {value}") def set_():    name = threading.current_thread().name    # 为不同的线程设置不同的值    if name == "one":        local.value = "ONE"    elif name == "two":        local.value = "TWO"    # 执行 get 函数    get() t1 = threading.Thread(target=set_, name="one")t2 = threading.Thread(target=set_, name="two")t1.start()t2.start()"""线程 one, value: ONE线程 two, value: TWO"""可以看到两个线程之间是互不影响的,因为每个线程都有自己唯一的 id,在绑定值的时候会绑定在当前的线程中,获取也会从当前的线程中获取。可以把 ThreadLocal 想象成一个字典:1234{    "one": {"value": "ONE"},    "two": {"value": "TWO"}}更准确的说 key 应该是线程的 id,为了直观我们就用线程的 name 代替了,但总之在获取的时候只会获取绑定在该线程上的变量的值。而 flask 内部也是这么设计的,只不过它没有直接用 threading.local,而是自己实现了一个 Local 类,除了支持线程之外还支持 greenlet 的协程,那么它是怎么实现的呢?首先我们知道 flask 内部存在 "请求 context" 和 "应用 context",它们都是通过栈来维护的(两个不同的栈)。123456# flask/globals.py_request_ctx_stack = LocalStack()_app_ctx_stack = LocalStack()current_app = LocalProxy(_find_app)request = LocalProxy(partial(_lookup_req_object, "request"))session = LocalProxy(partial(_lookup_req_object, "session"))每个请求都会绑定在当前的 Context 中,等到请求结束之后再销毁,这个过程由框架完成,开发者只需要直接使用 request 即可。所以请求的具体细节流程可以点进源码中查看,这里我们重点关注一个对象:werkzeug.local.Local,也就是上面说的 Local 类,它是变量的设置和获取的关键。直接看部分源码:12345678910111213141516171819202122232425262728293031323334353637383940414243# werkzeug/local.py class Local(object):    __slots__ = ("__storage__", "__ident_func__")     def __init__(self):        # 内部有两个成员:__storage__ 是一个字典,值就存在这里面        # __ident_func__ 只需要知道它是用来获取线程 id 的即可        object.__setattr__(self, "__storage__", {})        object.__setattr__(self, "__ident_func__", get_ident)     def __call__(self, proxy):        """Create a proxy for a name."""        return LocalProxy(self, proxy)     def __release_local__(self):        self.__storage__.pop(self.__ident_func__(), None)     def __getattr__(self, name):        try:            # 根据线程 id 得到 value(一个字典)            # 然后再根据 name 获取对应的值            # 所以只会获取绑定在当前线程上的值            return self.__storage__[self.__ident_func__()][name]        except KeyError:            raise AttributeError(name)     def __setattr__(self, name, value):        ident = self.__ident_func__()        storage = self.__storage__        try:            # 将线程 id 作为 key,然后将值设置在对应的字典中            # 所以只会将值设置在当前的线程中            storage[ident][name] = value        except KeyError:            storage[ident] = {name: value}     def __delattr__(self, name):        # 删除逻辑也很简单        try:            del self.__storage__[self.__ident_func__()][name]        except KeyError:            raise AttributeError(name)所以我们看到 flask 内部的逻辑其实很简单,通过 ThreadLocal 实现了线程之间的隔离。每个请求都会绑定在各自的 Context 中,获取值的时候也会从各自的 Context 中获取,因为它就是用来保存相关信息的(重要的是同时也实现了隔离)。相应此刻你已经理解了上下文,但是问题来了,不管是 threading.local 也好、还是类似于 flask 自己实现的 Local 也罢,它们都是针对线程的。如果是使用 async def 定义的协程该怎么办呢?如何实现每个协程的上下文隔离呢?所以终于引出了我们的主角:contextvars。contextvars该模块提供了一组接口,可用于在协程中管理、设置、访问局部 Context 的状态。12345678910111213141516171819202122232425import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试") async def get():    # 获取值    return c.get() + "~~~" async def set_(val):    # 设置值    c.set(val)    print(await get()) async def main():    coro1 = set_("协程1")    coro2 = set_("协程2")    await asyncio.gather(coro1, coro2)  asyncio.run(main())"""协程1~~~协程2~~~"""ContextVar 提供了两个方法,分别是 get 和 set,用于获取值和设置值。我们看到效果和 ThreadingLocal 类似,数据在协程之间是隔离的,不会受到彼此的影响。但我们再仔细观察一下,我们是在 set_ 函数中设置的值,然后在 get 函数中获取值。可 await get() 相当于是开启了一个新的协程,那么意味着设置值和获取值不是在同一个协程当中。但即便如此,我们依旧可以获取到希望的结果。因为 Python 的协程是无栈协程,通过 await 可以实现级联调用。我们不妨再套一层:123456789101112131415161718192021222324252627282930import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试") async def get1():    return await get2() async def get2():    return c.get() + "~~~" async def set_(val):    # 设置值    c.set(val)    print(await get1())    print(await get2()) async def main():    coro1 = set_("协程1")    coro2 = set_("协程2")    await asyncio.gather(coro1, coro2)  asyncio.run(main())"""协程1~~~协程1~~~协程2~~~协程2~~~"""我们看到不管是 await get1() 还是 await get2(),得到的都是 set_ 中设置的结果,说明它是可以嵌套的。并且在这个过程当中,可以重新设置值。12345678910111213141516171819202122232425262728293031323334353637383940import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试") async def get1():    c.set("重新设置")    return await get2() async def get2():    return c.get() + "~~~" async def set_(val):    # 设置值    c.set(val)    print("------------")    print(await get2())    print(await get1())    print(await get2())    print("------------") async def main():    coro1 = set_("协程1")    coro2 = set_("协程2")    await asyncio.gather(coro1, coro2)  asyncio.run(main())"""------------协程1~~~重新设置~~~重新设置~~~------------------------协程2~~~重新设置~~~重新设置~~~------------"""先 await get2() 得到的就是 set_ 函数中设置的值,这是符合预期的。但是我们在 get1 中将值重新设置了,那么之后不管是 await get1() 还是直接 await get2(),得到的都是新设置的值。这也说明了,一个协程内部 await 另一个协程,另一个协程内部 await 另另一个协程,不管套娃(await)多少次,它们获取的值都是一样的。并且在任意一个协程内部都可以重新设置值,然后获取会得到最后一次设置的值。再举个栗子:12345678910111213141516171819202122232425262728import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试") async def get1():    return await get2() async def get2():    val = c.get() + "~~~"    c.set("重新设置啦")    return val async def set_(val):    # 设置值    c.set(val)    print(await get1())    print(c.get()) async def main():    coro = set_("古明地觉")    await coro asyncio.run(main())"""古明地觉~~~重新设置啦"""await get1() 的时候会执行 await get2(),然后在里面拿到 c.set 设置的值,打印 "古明地觉~~~"。但是在 get2 里面,又将值重新设置了,所以第二个 print 打印的就是新设置的值。\如果在 get 之前没有先 set,那么会抛出一个 LookupError,所以 ContextVar 支持默认值:1234567891011121314151617181920import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试",                           default="哼哼") async def set_(val):    print(c.get())    c.set(val)    print(c.get()) async def main():    coro = set_("古明地觉")    await coro asyncio.run(main())"""哼哼古明地觉"""除了在 ContextVar 中指定默认值之外,也可以在 get 中指定:1234567891011121314151617181920import asyncioimport contextvars c = contextvars.ContextVar("只是一个标识, 用于调试",                           default="哼哼") async def set_(val):    print(c.get("古明地恋"))    c.set(val)    print(c.get()) async def main():    coro = set_("古明地觉")    await coro asyncio.run(main())"""古明地恋古明地觉"""所以结论如下,如果在 c.set 之前使用 c.get:当 ContextVar 和 get 中都没有指定默认值,会抛出 LookupError;只要有一方设置了,那么会得到默认值;如果都设置了,那么以 get 为准;如果 c.get 之前执行了 c.set,那么无论 ContextVar 和 get 有没有指定默认值,获取到的都是 c.set 设置的值。所以总的来说还是比较好理解的,并且 ContextVar 除了可以作用在协程上面,它也可以用在线程上面。没错,它可以替代 threading.local,我们来试一下:1234567891011121314151617181920212223242526import threadingimport contextvars c = contextvars.ContextVar("context_var") def get():    name = threading.current_thread().name    value = c.get()    print(f"线程 {name}, value: {value}") def set_():    name = threading.current_thread().name    if name == "one":        c.set("ONE")    elif name == "two":        c.set("TWO")    get() t1 = threading.Thread(target=set_, name="one")t2 = threading.Thread(target=set_, name="two")t1.start()t2.start()"""线程 one, value: ONE线程 two, value: TWO"""和 threading.local 的表现是一样的,但是更建议使用 ContextVars。不过前者可以绑定任意多个值,而后者只能绑定一个值(可以通过传递字典的方式解决这一点)。c.Token当我们调用 c.set 的时候,其实会返回一个 Token 对象:12345678import contextvars c = contextvars.ContextVar("context_var")token = c.set("val")print(token)"""<Token var=<ContextVar name='context_var' at 0x00..> at 0x00...>"""Token 对象有一个 var 属性,它是只读的,会返回指向此 token 的 ContextVar 对象。123456789101112import contextvars c = contextvars.ContextVar("context_var")token = c.set("val") print(token.var is c)  # Trueprint(token.var.get())  # val print(    token.var.set("val2").var.set("val3").var is c)  # Trueprint(c.get())  # val3Token 对象还有一个 old_value 属性,它会返回上一次 set 设置的值,如果是第一次 set,那么会返回一个 <Token.MISSING>。12345678910111213import contextvars c = contextvars.ContextVar("context_var")token = c.set("val") # 该 token 是第一次 c.set 所返回的# 在此之前没有 set,所以 old_value 是 <Token.MISSING>print(token.old_value)  # <Token.MISSING> token = c.set("val2")print(c.get())  # val2# 返回上一次 set 的值print(token.old_value)  # val那么这个 Token 对象有什么作用呢?从目前来看貌似没太大用处啊,其实它最大的用处就是和 reset 搭配使用,可以对状态进行重置。123456789101112131415161718import contextvars#### c = contextvars.ContextVar("context_var")token = c.set("val")# 显然是可以获取的print(c.get())  # val # 将其重置为 token 之前的状态# 但这个 token 是第一次 set 返回的# 那么之前就相当于没有 set 了c.reset(token)try:    c.get()  # 此时就会报错except LookupError:    print("报错啦")  # 报错啦 # 但是我们可以指定默认值print(c.get("默认值"))  # 默认值contextvars.Context它负责保存 ContextVars 对象和设置的值之间的映射,但是我们不会直接通过 contextvars.Context 来创建,而是通过 contentvars.copy_context 函数来创建。12345678910111213141516171819202122import contextvars c1 = contextvars.ContextVar("context_var1")c1.set("val1")c2 = contextvars.ContextVar("context_var2")c2.set("val2") # 此时得到的是所有 ContextVar 对象和设置的值之间的映射# 它实现了 collections.abc.Mapping 接口# 因此我们可以像操作字典一样操作它context = contextvars.copy_context()# key 就是对应的 ContextVar 对象,value 就是设置的值print(context[c1])  # val1print(context[c2])  # val2for ctx, value in context.items():    print(ctx.get(), ctx.name, value)    """    val1 context_var1 val1    val2 context_var2 val2    """ print(len(context))  # 2除此之外,context 还有一个 run 方法:1234567891011121314151617181920212223242526272829import contextvars c1 = contextvars.ContextVar("context_var1")c1.set("val1")c2 = contextvars.ContextVar("context_var2")c2.set("val2") context = contextvars.copy_context() def change(val1, val2):    c1.set(val1)    c2.set(val2)    print(c1.get(), context[c1])    print(c2.get(), context[c2]) # 在 change 函数内部,重新设置值# 然后里面打印的也是新设置的值context.run(change, "VAL1", "VAL2")"""VAL1 VAL1VAL2 VAL2""" print(c1.get(), context[c1])print(c2.get(), context[c2])"""val1 VAL1val2 VAL2"""我们看到 run 方法接收一个 callable,如果在里面修改了 ContextVar 实例设置的值,那么对于 ContextVar 而言只会在函数内部生效,一旦出了函数,那么还是原来的值。但是对于 Context 而言,它是会受到影响的,即便出了函数,也是新设置的值,因为它直接把内部的字典给修改了。小结以上就是 contextvars 模块的用法,在多个协程之间传递数据是非常方便的,并且也是并发安全的。如果你用过 Go 的话,你应该会发现和 Go 在 1.7 版本引入的 context 模块比较相似,当然 Go 的 context 模块功能要更强大一些,除了可以传递数据之外,对多个 goroutine 的级联管理也提供了非常清蒸的解决方案。总之对于 contextvars 而言,它传递的数据应该是多个协程之间需要共享的数据,像 cookie, session, token 之类的,比如上游接收了一个 token,然后不断地向下透传。但是不要把本应该作为函数参数的数据,也通过 contextvars 来传递,这样就有点本末倒置了。
  • [其他干货] CUDA编程(七)共享内存
    如何使用Shared Memory优化CUDA应用?Shared Memory的特点是快的时候特别快,慢的时候特别慢。什么时候快?同一warp中所有线程访问不同的banks或者 同一warp中所有线程读取同一地址(通过广播)什么时候慢?同一warp中多个线程访问同一个bank的不同地址(此时将产生 bank conflict)串行访问请注意:bank conflict发生的原因就是 warp的分配和bank的分配重叠了:如何避免bank conflict,简单的方法是Padding法(好像叫做补边):通过增加一个空列,让bank强行错位,使得每段连续的数据被分配到不同的bank中。具体做法很简单:就是在设置Shared Memory的时候,不设置成 方阵BLOCK_SIZE X BLOCK_SIZE,而设置成 BLOCK_SIZE X (BLOCK_SIZE+1).最后,我们可以使用Shared Memory优化mXn, nXk的矩阵乘 的代码,提高访存的效率。具体方法如下:申请两块 Shared Memory,都是BLOCK_SIZE X BLOCK_SIZE 大小。一个沿着矩阵mXn滑动,一个沿着矩阵 nXk滑动。将 子集的结果累加到 目的矩阵中:具体的代码如下:__global__ void gpu_matrix_mult_shared(int *d_a, int *d_b, int *d_result, int m, int n, int k) { __shared__ int tile_a[BLOCK_SIZE][BLOCK_SIZE]; __shared__ int tile_b[BLOCK_SIZE][BLOCK_SIZE]; int row = blockIdx.y * BLOCK_SIZE + threadIdx.y; int col = blockIdx.x * BLOCK_SIZE + threadIdx.x; int tmp = 0; int idx; for (int sub = 0; sub < gridDim.x; ++sub) { idx = row * n + sub * BLOCK_SIZE + threadIdx.x; tile_a[threadIdx.y][threadIdx.x] = row并将前面 代码中调用矩阵乘的地方:gpu_matrix_mult<<<dimGrid, dimBlock>>>(d_a, d_b, d_c, m, n, k); 改为gpu_matrix_mult_shared<<<dimGrid, dimBlock>>>(d_a, d_b, d_c, m, n, k); 其余不变。开始编译,在Jetson Nano B01上执行:比较前面的矩阵乘代码,start-》stop:Time = 2.25109 ms 时间略有下降。张小白于是修改blocksize,将其分别改为 16,8,4,再进行统计汇总:矩阵MXN(m)矩阵NXK(n)矩阵NXK(k)blocksizestop-start(ms)100100100321.83286100100100161.2736510010010081.2329210010010043.528651001001006(补测)2.199910010010012(补测)1.34755从上面的结果来看,blocksize为8,16,32时好像差异不大,但是blocksize为4的时候速度降得比较厉害。从100为4的倍数来看。貌似是这个时候wrap和bank重叠了。那我们使用Padding大法看看:将tile_a和tile_b的方阵改为补边的方阵:好像效果也不是很好。注:在blocksize为4时,其实并没有发生bank conflict!而只是因为4X4,只有16个线程,而一个warp需要32个线程,所以相当于计算时,有一半算力被浪费掉了,进而速度慢了一倍。欢老师建议,至少应该NXN>32比较好。于是张小白将blocksize设成6,又试了一下,结果插入了上述表格。当然,速度还是略有下降(下面也是一样)。我个人猜测,如果是六六三十六,其实32个线程一个warp,反而需要2个warp才能完成工作,所以速度还是不行。张小白猜想应该把blocksize的平方设成32的倍数是最合适的。比如八八六十四。。。12X12=32X4.5,好像也不大合适。。但是可能会因为使用较多而速度略有提高(事实证明好像也是如此)张小白担心是矩阵太小的缘故,将 矩阵从100改为1000试试。但是发现一旦改为1000后,CPU计算可能算不过来了:只好将CPU那部分代码和后面比较的代码屏蔽掉。再重新统计:矩阵MXN(m)矩阵NXK(n)矩阵NXK(k)blocksizestop-start(ms)10001000100032265.10610001000100016228.091000100010008202.3821000100010004518.3151000100010006(补测)386.17110001000100012(补测)246.29张小白用Padding法试了一下:好像也没有得到提速的效果:(反而更慢了)注:其实并没有发生什么bank conflict,都是张小白心里在YY。。
  • [环境搭建] 【FI-8122】【集群扩容】询问扩容时配置规划填写IP规划
    各位老师好,FI版本8.1.2.2,集群需要扩容,在填写集群配置规划中的IP规划有一些疑问,希望能够得到解答疑问:集群需要扩容3个CN节点以及1200个DN节点,如何编写配置规划文档中的IP规划【功能模块】集群配置规划文档【操作步骤&问题现象】1、按照3ms上的一篇帖子(http://3ms.huawei.com/km/blogs/details/9738513)基础配置填写如下基础配置: Ø  集群名称:自定义   Ø  安装模式:选择“集群扩容” Ø  认证模式:局点自行决定,建议使用“安全模式”Ø  是否自定义套餐:YES   Ø  扩容节点数量:根据真实情况填写IP规划与进程部署: Ø  类型:根据局点情况填写要扩容的节点:MN(管理节点)/CN(控制节点)/DN(数据节点)2、以上步骤操作完,IP规划详情如下,所有节点均为DN节点(实际想要达到的效果是需要有CN节点)疑问:如果要添加CN节点是请问是直接把14行的DN改成CN吗?然后再勾选相关进程?我通过这样的方式填写配置规划文档,生成的preinstall.ini 中新扩的三个CN节点对应的host0.ini host1.ini  host2.ini 中并没有对应配置3、注意到9行到14行中有隐藏的行,手动拉出如下显示疑问:不是改第14行,而是改隐藏起来的这里吗?如果是改隐藏行,那么MN需要填写吗?总的来说就是不懂扩容CN+DN节点,集群配置规划要如何填写3ms上只找到了这一篇内容,但是没有详细说明http://3ms.huawei.com/km/blogs/details/9738513
  • [技术干货] 多线程:如何确定所有任务都执行完成了【转】
    今天学习到了一个比较强大的类:ExecutorCompletionService,它是将 Executor和BlockQueue结合的jdk类,其实现的主要目的是:提交任务线程,每一个线程任务直线完成后,将返回值放在阻塞队列中,然后可以通过阻塞队列的take()方法返回 对应线程的执行结果!!所以还可以这样写: ExecutorCompletionService<String> completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(5)); for(int i=0; i<10; i++) { int j = i; completionService.submit(()-> Thread.currentThread().getName() + "------>" + j); } try { for(int i=0; i<10; i++) { Future<String> future = completionService.take(); if(future != null) { String str = future.get(); System.out.println(str); } } } catch (Exception e) { e.printStackTrace(); } System.out.println("---------->结束"); 同样可以达到阻塞的效果!(注:其中有用到jdk1.8的lambda表达式~)--------------------------------------------------------------------------------------------------------- 之前我有写过一篇博客,是关于多线程写同一个sheet文件的。类似的场景很多,当我们想用多线程提高效率时,面临的关键问题就是线程安全和确定所有任务都完成。线程安全的问题那篇博客有说,就是确保对公共资源的写操作是安全的,比如List的add操作采用synchronized来包装或直接采用线程安全的集合;Sheet的addRow加锁等… 而本篇的重点是“如何确保所有任务都完成,才能进行下一步?”。 先来看现象: public static void m() { for(int i=0; i<10; i++) { int j = i; new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "------>" + j); } }).start(); } System.out.println("---------->结束"); } 这段代码创建了10个线程,每个线程的任务会打印当前线程的名字,运行后发现可能出现以下结果(顺序不一定是下面这样): Thread-1------>1---------->结束Thread-0------>0Thread-2------>2Thread-4------>4Thread-3------>3Thread-5------>5Thread-6------>6Thread-7------>7Thread-8------>8Thread-9------>9会发现“---------->结束”没有在所有线程都运行完就打印出来了,映射到实际场景就是用多线程去帮我们干活,还没干完呢就直接下一步了,如此的话没有实际意义(除非这个多线程的任务是异步的,其他逻辑不需要等待它完成才能进行)。 我们知道,多线程执行任务可以用原始的线程提交(上述代码),也可以用线程池(比较推荐这种方式,便于对线程进行管理)。为了解决上述问题,可以用CountDownLatch计数器,计数器的初始大小要跟任务数的大小一致(跟线程数无关),每执行一次任务,计数器减一(countDown),await()方法会一直阻塞主线程,直到计数器的值减为0,才会释放锁,如此便可以达到确保所有任务都完成才继续下一步的效果。 先用原始线程结合计数器的方式来试试效果: public static void m1() { CountDownLatch latch = new CountDownLatch(10); for(int i=0; i<10; i++) { int j = i; new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "------>" + j); latch.countDown(); } }).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("---------->结束"); } 无论运行多少次,会发现"---------->结束"始终会在多线程所有任务都执行完毕后打印,比如某次结果: Thread-0------>0Thread-1------>1Thread-2------>2Thread-3------>3Thread-4------>4Thread-7------>7Thread-8------>8Thread-9------>9Thread-5------>5Thread-6------>6---------->结束 再用线程池结合计数器的方式来尝试: public static void m2() { CountDownLatch latch = new CountDownLatch(10); ExecutorService es = Executors.newFixedThreadPool(5); for(int i=0; i<10; i++) { int j = i; es.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "------>" + j); latch.countDown(); } }); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("---------->结束"); } 同样地,无论运行多少次,"---------->结束"都会在所有任务都完成以后再进行!比如某次打印结果为:pool-1-thread-2------>1pool-1-thread-2------>5pool-1-thread-3------>2pool-1-thread-1------>0pool-1-thread-1------>8pool-1-thread-1------>9pool-1-thread-3------>7pool-1-thread-4------>3pool-1-thread-2------>6pool-1-thread-5------>4---------->结束 这充分印证了CountDownLatch计数器的强大!下面我们再看一个比较容易忽略的方式: public static void m3() { ExecutorService es = Executors.newFixedThreadPool(5); List<Future<String>> list = new ArrayList<>(); for(int i=0; i<10; i++) { int j = i; Future<String> future = es.submit(new Callable<String>() { @Override public String call() throws Exception { return Thread.currentThread().getName() + "------>" + j; } }); list.add(future); } try { for(Future<String> future : list) { System.out.println(future.get()); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("---------->结束"); } Callable不用多说,它可以表示一个有返回值的线程,Future则用于接收返回的结果。Future的get方法具有阻塞作用,它会一直阻塞直至获取到结果。Callable&Future一般都是结合线程池来使用。运行多次,也会发现"---------->结束"总是在最后运行的,同样达到了目的。 说到线程池管理线程,需要注意的是比如: Executors.newFixedThreadPool(40) 实际上是new了一个corePoolSize=maximumPoolSize的特殊情况的线程池: public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } Executors提供的静态方法创建线程池,内部都是去构造一个ThreadPoolExecutor,只是不同类型的线程池, 2、线程池类型以及提交线程的过程:3、常见的线程相关类的关系图:corePoolSize和maximumPoolSize的大小关系不同,还有采用的任务队列的类型也不同。
  • [知识分享] 一种比读写锁更快的锁,还不赶紧认识一下
    【摘要】 一起来聊聊这个在高并发环境下比ReadWriteLock更快的锁——StampedLock。本文分享自华为云社区《【高并发】高并发场景下一种比读写锁更快的锁,看完我彻底折服了!!》,作者:冰 河 。什么是StampedLock?ReadWriteLock锁允许多个线程同时读取共享变量,但是在读取共享变量的时候,不允许另外的线程多共享变量进行写操作,更多的适合于读多写少的环境中。那么,在读多写少的环境中,有没有一种比ReadWriteLock更快的锁呢?答案当然是有!那就是我们今天要介绍的主角——JDK1.8中新增的StampedLock!没错,就是它!StampedLock与ReadWriteLock相比,在读的过程中也允许后面的一个线程获取写锁对共享变量进行写操作,为了避免读取的数据不一致,使用StampedLock读取共享变量时,需要对共享变量进行是否有写入的检验操作,并且这种读是一种乐观读。总之,StampedLock是一种在读取共享变量的过程中,允许后面的一个线程获取写锁对共享变量进行写操作,使用乐观读避免数据不一致的问题,并且在读多写少的高并发环境下,比ReadWriteLock更快的一种锁。StampedLock三种锁模式这里,我们可以简单对比下StampedLock与ReadWriteLock,ReadWriteLock支持两种锁模式:一种是读锁,另一种是写锁,并且ReadWriteLock允许多个线程同时读共享变量,在读时,不允许写,在写时,不允许读,读和写是互斥的,所以,ReadWriteLock中的读锁,更多的是指悲观读锁。StampedLock支持三种锁模式:写锁、读锁(这里的读锁指的是悲观读锁)和乐观读(很多资料和书籍写的是乐观读锁,这里我个人觉得更准确的是乐观读,为啥呢?我们继续往下看啊)。其中,写锁和读锁与ReadWriteLock中的语义类似,允许多个线程同时获取读锁,但是只允许一个线程获取写锁,写锁和读锁也是互斥的。另一个与ReadWriteLock不同的地方在于:StampedLock在获取读锁或者写锁成功后,都会返回一个Long类型的变量,之后在释放锁时,需要传入这个Long类型的变量。例如,下面的伪代码所示的逻辑演示了StampedLock如何获取锁和释放锁。public class StampedLockDemo{ //创建StampedLock锁对象 public StampedLock stampedLock = new StampedLock(); //获取、释放读锁 public void testGetAndReleaseReadLock(){ long stamp = stampedLock.readLock(); try{ //执行获取读锁后的业务逻辑 }finally{ //释放锁 stampedLock.unlockRead(stamp); } } //获取、释放写锁 public void testGetAndReleaseWriteLock(){ long stamp = stampedLock.writeLock(); try{ //执行获取写锁后的业务逻辑。 }finally{ //释放锁 stampedLock.unlockWrite(stamp); } } }StampedLock支持乐观读,这是它比ReadWriteLock性能要好的关键所在。 ReadWriteLock在读取共享变量时,所有对共享变量的写操作都会被阻塞。而StampedLock提供的乐观读,在多个线程读取共享变量时,允许一个线程对共享变量进行写操作。我们再来看一下JDK官方给出的StampedLock示例,如下所示。class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); double currentX = x, currentY = y; if (!sl.validate(stamp)) { stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } void moveIfAtOrigin(double newX, double newY) { // upgrade // Could instead start with optimistic, not read mode long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { long ws = sl.tryConvertToWriteLock(stamp); if (ws != 0L) { stamp = ws; x = newX; y = newY; break; } else { sl.unlockRead(stamp); stamp = sl.writeLock(); } } } finally { sl.unlock(stamp); } } }在上述代码中,如果在执行乐观读操作时,另外的线程对共享变量进行了写操作,则会把乐观读升级为悲观读锁,如下代码片段所示。double distanceFromOrigin() { // A read-only method //乐观读 long stamp = sl.tryOptimisticRead(); double currentX = x, currentY = y; //判断是否有线程对变量进行了写操作 //如果有线程对共享变量进行了写操作 //则sl.validate(stamp)会返回false if (!sl.validate(stamp)) { //将乐观读升级为悲观读锁 stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { //释放悲观锁 sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); }这种将乐观读升级为悲观读锁的方式相比一直使用乐观读的方式更加合理,如果不升级为悲观读锁,则程序会在一个循环中反复执行乐观读操作,直到乐观读操作期间没有线程执行写操作,而在循环中不断的执行乐观读会消耗大量的CPU资源,升级为悲观读锁是更加合理的一种方式。StampedLock实现思想StampedLock内部是基于CLH锁实现的,CLH是一种自旋锁,能够保证没有“饥饿现象”的发生,并且能够保证FIFO(先进先出)的服务顺序。在CLH中,锁维护一个等待线程队列,所有申请锁,但是没有成功的线程都会存入这个队列中,每一个节点代表一个线程,保存一个标记位(locked),用于判断当前线程是否已经释放锁,当locked标记位为true时, 表示获取到锁,当locked标记位为false时,表示成功释放了锁。当一个线程试图获得锁时,取得等待队列的尾部节点作为其前序节点,并使用类似如下代码判断前序节点是否已经成功释放锁:while (pred.locked) { //省略操作 }只要前序节点(pred)没有释放锁,则表示当前线程还不能继续执行,因此会自旋等待;反之,如果前序线程已经释放锁,则当前线程可以继续执行。释放锁时,也遵循这个逻辑,线程会将自身节点的locked位置标记为false,后续等待的线程就能继续执行了,也就是已经释放了锁。StampedLock的实现思想总体来说,还是比较简单的,这里就不展开讲了。StampedLock的注意事项在读多写少的高并发环境下,StampedLock的性能确实不错,但是它不能够完全取代ReadWriteLock。在使用的时候,也需要特别注意以下几个方面。StampedLock不支持重入没错,StampedLock是不支持重入的,也就是说,在使用StampedLock时,不能嵌套使用,这点在使用时要特别注意。StampedLock不支持条件变量第二个需要注意的是就是StampedLock不支持条件变量,无论是读锁还是写锁,都不支持条件变量。StampedLock使用不当会导致CPU飙升这点也是最重要的一点,在使用时需要特别注意:如果某个线程阻塞在StampedLock的readLock()或者writeLock()方法上时,此时调用阻塞线程的interrupt()方法中断线程,会导致CPU飙升到100%。例如,下面的代码所示。public void testStampedLock() throws Exception{ final StampedLock lock = new StampedLock(); Thread thread01 = new Thread(()->{ // 获取写锁 lock.writeLock(); // 永远阻塞在此处,不释放写锁 LockSupport.park(); }); thread01.start(); // 保证thread01获取写锁 Thread.sleep(100); Thread thread02 = new Thread(()-> //阻塞在悲观读锁 lock.readLock() ); thread02.start(); // 保证T2阻塞在读锁 Thread.sleep(100); //中断线程thread02 //会导致线程thread02所在CPU飙升 thread02.interrupt(); thread02.join(); }运行上面的程序,会导致thread02线程所在的CPU飙升到100%。这里,有很多小伙伴不太明白为啥LockSupport.park();会导致thread01会永远阻塞。这里,冰河为你画了一张线程的生命周期图,如下所示。这下明白了吧?在线程的生命周期中,有几个重要的状态需要说明一下。NEW:初始状态,线程被构建,但是还没有调用start()方法。RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。TIME_WAITING:超时等待状态。可以在一定的时间自行返回。TERMINATED:终止状态,当前线程执行完毕。看完这个线程的生命周期图,知道为啥调用LockSupport.park();会使thread02阻塞了吧?所以,在使用StampedLock时,一定要注意避免线程所在的CPU飙升的问题。那如何避免呢?那就是使用StampedLock的readLock()方法或者读锁和使用writeLock()方法获取写锁时,一定不要调用线程的中断方法来中断线程,如果不可避免的要中断线程的话,一定要用StampedLock的readLockInterruptibly()方法获取可中断的读锁和使用StampedLock的writeLockInterruptibly()方法获取可中断的悲观写锁。最后,对于StampedLock的使用,JDK官方给出的StampedLock示例本身就是一个最佳实践了,小伙伴们可以多看看JDK官方给出的StampedLock示例,多多体会下StampedLock的使用方式和背后原理与核心思想。
  • [行业资讯] 雄安绿色智慧建设全面提速,“数字基因”融入设计建造过程
    如此体量庞大、意义重大的项目,施工中从工人发现问题到反馈至决策方,通常需要八九个环节,耗时两天左右。在‘PP-DSTC’应用体系下,整个过程扁平至三四个环节,最多仅需4个小时左右就能完成,大大提高了工作效率。”中建八局雄安商务服务中心二标项目技术负责人赵趯说。今年是雄安新区设立五周年,也是新区“显雏形,展形象”的关键之年。在建设过程中,雄安绿色智慧建设已全面提速。雄安新区改革发展局信息组相关负责人表示,为贯彻落实《河北雄安新区规划纲要》,雄安新区形成了“双基建”同步建设的路径,建设项目中“新基建”投资额已占相当比重。BIM(建筑信息模型技术)等先进技术在雄安新区得到大规模运用,“数字基因”已融入新区项目设计和建造过程中。建设进程实现人与物全面感知建筑面积72.52万平方米的雄安商务服务中心,是雄安新区第一个标志性城市综合体,也是承接北京非首都功能疏解和北京企业先期入驻的职住一体化综合园区。目前,商务服务中心基本已全部竣工交付,非首都功能疏解企业正陆续入驻。商务服务中心项目不但施工体量庞大,工期也十分紧迫,要求720天竣工交付。为了尽早交付,建设者不分昼夜,与时间赛跑。项目二标连续16个月产值过亿,商服会展中心配套商务酒店更提前140天交付。“智慧”二字贯穿了项目建设进程的始终。赵趯对北京日报客户端记者说,在智慧建造管理手段的帮助下,商服二标项目所有施工图的校核、翻样和深化设计在三个月内就得以完成。全部图纸摞在一起,足有760公斤重。“PP-DSTC”BIM实施应用体系专为商服项目搭建,作为一种新型协同软件生态,“PP-DSTC”汇集了BIM、大数据分析处理、云计算、物联网等技术,使商服在建设进程中实现了人与物全面感知,各环节工作互联互通、信息协同共享。项目的数据收集涵盖了人、机器、材料、安全、质量等各个环节,实现了项目联动性管理。“如果数据显示某台塔吊的工作量低于其他塔吊,那就说明我们的区域部署存在问题,导致塔机工作量不饱满。这时我们就可以及时查找症结,作出调整。”赵趯举例说。数智建造应用助力技术交底雄安新区科创综合服务中心项目一期工程5万多平方米的三栋建筑已于近期封顶。中心(一期)是雄安新区启动区科学园片区的标志性建筑,是承接北京非首都功能疏解的科技创新平台和科研机构。项目建成后,将引导北京科研机构创新平台有序向雄安布局,形成国家科研机构与区域创新协同发展新模式。承担新区推进协同创新任务的科创综合服务中心,在建设过程中的科技含量也一点不低。工地上一派热火朝天的建设场面,项目的信息化展示中心内,中铁建设科创综合服务中心(一期)项目执行经理李阳正带领各班组组长,通过三维建模演示进行技术交底。利用中铁建设自主研发的数智建造应用,三维建模各道施工工序的质量控制样板都清晰地呈现在屏幕上,包括墙柱梁板钢筋、主体结构样板、砌筑样板、屋面样板等十几种模板展示。点击之后,会出现三维立体的360度模型,每一道工序的施工顺序的步骤分解也一目了然。李阳表示,项目建设过程中,各班组长必须将各自负责的标准流程熟记于心,带领各自班组高效率、高质量施工。数智建造应用中的AR实景交互模拟应用使技术交底的效率明显提升。“小家伙”发挥“大能量”雄安·金湖未来城项目位于雄安新区容东片区西南部的容东片区3号地块,与雄安市民服务中心、商务服务中心相邻。项目建成后,将打造高端城市会客厅和金融产业园,助力容东片区建设成为宜居宜业、协调融合、绿色智能的综合性功能区。中建二局雄安·金湖未来城项目四标段南区项目总工郑兴东告诉北京日报客户端记者,他所负责的四标段南区项目目前已接近尾声,高效安全的施工,一个“小家伙”功不可没。精致的外型,轻巧的行动,灵敏的反应……这就是项目工地上的“群宠”——智能巡检机器狗。郑兴东介绍,智能巡检机器狗具备较强的运动能力和视觉能力,能代替人力进入施工现场的大部分区域,特别是一些密闭空间等危险或不便进入的区域,都是它大显身手的地方。“作为一项高度智能化的产品,智能巡检机器狗还支持二次开发,可集成多个模块。”郑兴东举例说,在集成不安全行为识别模块后,智能巡检机器狗就变身为了安全巡检机器狗。这名“小小安全员”在工地特定区域进行安全监管,对不戴安全帽、未穿反光衣等不安全行为提出预警,发挥了“大能量”。
  • [交流吐槽] 第五次笔记
    ## 之前一直遇到的问题 - 描述 - MobaXterm无法重新进行主机连接,每次都报下图错误然后就只能从虚拟机获取IP全部重新来一遍,而且每次获取的IP都是不一样的 - ![e0b87f446be1a3071bd6a8d17765762b.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/28/1658944950026384561.png) - 希望达到的效果 - ip 地址不变,RaiDrive 直接重新连接主机 - 解决方法 - 虚拟机设置成下图 - ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/28/1658945154533335057.png) ## 任务管理 - 介绍 - 概念: 1. 从系统的角度看,任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行。 2. LiteOS 的任务模块可以给用户提供多个任务,实现了任务之间的切换和通信,帮助用户管理业务程序流程。这样用户可以将更多的精力投入到业务功能的实现中。 3. LiteOS 中的任务是**抢占式调度机制**,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度,同时支持时间片轮转调度方式。 4. LiteOS的任务默认有32个优先级(0-31),最高优先级为0,最低优先级为31。 - 任务状态 - 就绪(Ready): 该任务在就绪列表中,只**等待CPU**。 - 运行(Running): 该任务**正在执行**。 - 阻塞(Blocked): 该任务**不在就绪列表中**。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等。 - 退出态(Dead): 该任务**运行结束**,等待系统回收资源。 - 词条补充 - 任务ID: 在任务创建时通过参数返回给用户,作为任务的一个非常重要的标识。 - 任务优先级: 优先级表示任务执行的优先顺序。 - 任务**入口函数**: 每个新任务得到调度后将执行的函数。 - 任务控制块TCB: 每一个任务都含有一个任务控制块(TCB)。TCB包含了任务上下文栈指针(stack pointer)、任务状态、任务优先级、任务ID、任务名、任务栈大小等信息。**TCB可以反映出每个任务运行情况**。 - 任务栈: 每一个任务都拥有一个独立的栈空间,我们称为任务栈。 - 任务上下文: 任务在运行过程中使用到的一些资源,如寄存器等,我们称为任务上下文。LiteOS 在任务挂起的时候会将本任务的任务上下文信息,保存在自己的任务栈里面,以便任务恢复后,从栈空间中恢复挂起时的上下文信息,从而继续执行被挂起时被打断的代码。 - 任务切换: 任务切换包含获取就绪列表中最高优先级任务、切出任务上下文保存、切入任务上下文恢复等动作。 - 任务调度机制 - 任务状态迁移说明: - 就绪态 → 运行态 : 任务创建后进入就绪态,发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态,但此刻该任务依旧在就绪列表中。 - 运行态→阻塞态:任务运行因挂起、读信号量等待等,在就绪列表中被删除进入阻塞。 - 阻塞态→就绪态(阻塞态→运行态)∶阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;此时如果被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务由就绪态变成运行态。 - 就绪态→阻塞态:任务也有可能在就绪态时被阻塞(挂起)。 - 运行态→就绪态:有更高优先级任务创建或者恢复后,发生任务切换而进入就绪列表。 - 运行态→退出态:任务运行结束,内核自动将此任务删除,此时由运行态变为退出态。 - 阻塞态→退出态:阻塞的任务调用删除接口,任务状态由阻塞态变为退出态。 - cmsis_os2的API任务接口简介: - |接口名|功能描述| |--|--| |osThreadNew|创建任务| |osThreadTerminate|删除某个任务(一般是对非自任务操作)| |osThreadSuspend|任务挂起| |osThreadResume|任务恢复| - 创建任务: - ![5d44fceba1ee8ce629f7570ed41208ee.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/28/1658945287785237712.png)``` osThreadNew(osThreadFunc_t func,void * argument,const osThreadAttr_t * attr) ``` - 删除某个任务: ``` osThreadTerminate(osThreadld_t thread_id); ``` - 任务挂起: ``` osThreadSuspend(osThreadld_t thread_id) ``` - 任务恢复: ``` osThreadResume (osThreadld_t thread_id) ``` - 实现任务管理 - 实验结果与扩展 ## 软件定时器 - 定时器相关概念 - 软件定时器,是基于系统 Tick 时钟中断且由软件来模拟的定时器,当经过设定的 Tick 时钟计数值后会触发用户定义的回调函数。定时精度与系统Tick时钟的周期有关。硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,因此为了满足用户需求,提供更多的定时器,LiteOS 操作系统提供软件定时器功能。软件定时器扩展了定时器的数量,允许创建更多的定时业务。软件定时器功能上支持: - 静态裁剪:能通过宏关闭软件定时器功能。 - 软件定时器创建。 - 软件定时器启动。 - 软件定时器停止。 - 软件定时器删除。 - 软件定时器剩余Tick数获取。 - 运作机制 - 软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出。定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。 - 软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,Huawei LiteOS会根据当前系统Tick时间及用户设置的定时间隔确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时全局链表。当Tick中断到来时,在Tick中断处理函数中扫描软件定时器的计时全局链表,看是否有定时器超时,若有则将超时的定时器记录下来。 Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用之前记录下来的定时器的超时回调函数。 - 实现软件定时器创建 - cmsis os2的API软件定时器接口简介 - |接口名|功能描述| |--|--| |osTimerNew|创建定时器| |osTimerStart|启动定时器| |osTimerStop|停止定时器| |osTimerDelete|删除定时器| - 创建定时器 ``` osTimerNew (osTimerFunc_t func, osTimerType_t type, void *argument, const osTimerAttr t *attr); ``` - 启动定时器 ``` osTimerStart (osTimerld_t timer_id, uint32_t ticks); ``` - 停止定时器 ``` osTimerStop (osTimerld_t timer_id); ``` - 删除定时器 ``` osTimerDelete (osTimerld_t timer_id); ``` ## 信号量 - 基本概念 - 信号量(Semaphore)是一种实现任务间通信的机制,实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。 - 在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持3、通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。其值的含义分两种情况 1. 0,表示没有积累下来的Post信号量操作,且有可能有在此信号量上阻塞的任务。 2. 正值,表示有一个或多个Post信号量操作。 - 以同步为目的的信号量和以互斥为目的的信号量在使用有如下不同: 1. 用作互斥时,信号量创建后记数是满的,在需要使用临界资源时,先取信号量,使其变空,这样其他任务需要使用临界资源时就会因为无法取到信号量而阻塞,从而保证了临界资源的安全。 2. 用作同步时,信号量在创建后被置为空,任务1取信号量而阻塞,任务2在某种条件发生后,释放信号量,于是任务1得以进入READY或RUNNING态,从而达到了两个任务间的同步。 - 运作原理 1. 信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,受内存限制),并把所有的信号量初始化成未使用,并加入到未使用链表中供系统使用。 2. 信号量创建,从未使用的信号量链表中获取一个信号量资源,并设定初值。 3. 信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。 4. 信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。 5. 信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。 - 信号量运作机制 - 信号量允许多个任务在同一时刻访问同一资源,但会限制同一时刻访问此资源的最大任务数目。访问同一资源的任务数达到该资源的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。 - 公共资源有四个任务数,信号量都分别被线程1、2、3、4获取后,此时此资源就会锁定而不让线程5进入,线程5及后面的线程都进入阻塞模式,当线程1工作完成而释放出信号量,线程5立即获得信号而得到执行。如此往复。 - 运作示意图:![e9c744d344ba01def54a35983abfa7fa.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/28/1658944795484573505.png)
  • [技术干货] 预处理等有关知识
    ## 预处理 在一句话之前有一个#键,则这就叫做预处理。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658494655704989372.png) include 包含 引号里面是文件名,注意不是C++语言,所以没有尾部的分号。 ## define的用法 #define 用来替换代码 替换的内容前面再出现#变成加双引号 替换的内容中间出现##则变成前后字符串组合成长字符串 仿真连接:https://wokwi.com/projects/337884605578216019 ## 预处理条件判断的经典三种用法 #if #ifndef #elif #endif 1.防止出错用 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658494231653680930.png) 防止EXTRA_H被多次读取 2..define+型号 判读是否是这个板子型号 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658494408854223864.png) 作用:防止报错,根据不同的开发板执行不同的命令 3.输出debug占空间 方法:用一个ifdebug进行判断 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658494583708668209.png) # signd & unsignd、U & L formatters ## Signed vs Unsigned 默认情况下,变量的可正可负(Signed)。通过Unsigned修饰符,将变量强制为只能表示正数。这样子,在相同的内存空间下,正数的表示范围翻倍。 注意: (1) Signed代表负号,Unsigned代表没有负号 (2) 如果变量不可能为负,申明为unsigned (3) Unsigned 正数范围翻倍 (4) #define后面只跟一个参数定义为空 若数据范围溢出,则从原来的最大数值变为0,以后再继续加一。 如仿真所示 仿真:https://wokwi.com/projects/337889444446077523 ## U & L formatters U代表正数,L代表Long,UL代表正数Long 仿真:https://wokwi.com/projects/337889773226033747 ## 挥发性变量 volatile、random 随机数 1.挥发性变量 volatile 主要是用再多线程中对共同的变量进行访问时,该变量需要定义为挥发性变量。 在ARDUINO中多线程的情况只有一个,就是中断。所以,在中断中对全区变量的修改,需要将该变量定义为挥发性变量。 定义为挥发性变量后,会有两个作用。 (1).让GCC编译器不要耍小聪明,所以的易挥发性变量必须在内存中分配; (2).易挥发性变量的每次读写必须从内存中获取,不得使用CACHE或者寄存器内的数值。 2.中断易挥发性变量 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658495969592619802.png) 记忆点: (1)volatile易挥发性 non——volatile 不易挥发性 volatile断电不会存在 non——volatile断电文件仍然存在,如硬盘 (2)volatile变量必须从内存中读取 (3)volatile int定义变量 不会出错 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658496228483221761.png) ## random() 随机数 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/22/1658496357283246811.png) Random(300)=Random(0,300)
  • HCS形态,大量dhclient进程,业务慢,OS重启现象,softlockup,hung task
    关键词:dhclient,业务慢,OS重启,softlockup,hung task # 【触发场景】 HCS形态,强制下电触发;及其他场景 # 【业务现象】 业务最近变慢,OS hung,OS 重启 # 【分析】1. ps -ef | grep dhclient 输出大量dhclient相关进程2. 若OS已自重启,且生成vmcore,查看grep dhclient /var/crash/vmcore-dmesg.txt,结果类似如下[368606.181774] audit: type=1400 audit(1655663430.716:39128): avc: denied { read } for pid=96740 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368606.181780] audit: type=1400 audit(1655663430.716:39129): avc: denied { write } for pid=96740 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368613.134907] audit: type=1400 audit(1655663437.668:39130): avc: denied { write } for pid=97612 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368641.850946] audit: type=1400 audit(1655663466.384:39131): avc: denied { read } for pid=3875 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368641.850955] audit: type=1400 audit(1655663466.384:39132): avc: denied { write } for pid=3875 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368650.363477] audit: type=1400 audit(1655663474.900:39133): avc: denied { write } for pid=4655 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368666.926809] audit: type=1400 audit(1655663491.460:39134): avc: denied { read } for pid=7011 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368666.926815] audit: type=1400 audit(1655663491.460:39135): avc: denied { write } for pid=7011 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368672.397071] audit: type=1400 audit(1655663496.933:39136): avc: denied { write } for pid=7870 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368702.232288] audit: type=1400 audit(1655663526.769:39137): avc: denied { read } for pid=12391 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368702.232294] audit: type=1400 audit(1655663526.769:39138): avc: denied { write } for pid=12391 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368706.585507] audit: type=1400 audit(1655663531.121:39139): avc: denied { write } for pid=12764 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368722.100025] audit: type=1400 audit(1655663546.637:39140): avc: denied { read } for pid=15279 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368722.100028] audit: type=1400 audit(1655663546.637:39141): avc: denied { write } for pid=15279 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 scontext=system_u:system_r:dhcpc_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=file permissive=0 [368726.506184] audit: type=1400 audit(1655663551.041:39142): avc: denied { write } for pid=15659 comm="dhclient" name="dhclient-bond0.pid" dev="tmpfs" ino=1546 3. vmcore-dmesg中关键词[last unloaded: sysmonitor]4. OS软锁发生[400092.069204] WARN: soft lockup - CPU#61 stuck for 11s! [gaussdb:74740] [400104.069135] watchdog: BUG: soft lockup - CPU#61 stuck for 22s! [gaussdb:74740] [400105.167920] Kernel panic - not syncing: softlockup: hung tasks【解决】1. root下注释/rds/mgntAgent/v8.1.1.x/os/linux/netcardMonitor.sh中66~77行重启网卡代码段66 for card in $cardList; do ... 77 done2. ps -ef | grep dhclient | grep -v grep | awk '{print $2}' |xargs kill -9 3. 修改其他节点(步骤1),预防类似问题发生4. 均衡集群操作