• [技术干货] 多线程进阶:常见数据结构的安全性分析-转载
    一、常见数据结构非线程安全的数据结构: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 来传递,这样就有点本末倒置了。
  • [环境搭建] 【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的使用方式和背后原理与核心思想。
  • [技术干货] 预处理等有关知识
    ## 预处理 在一句话之前有一个#键,则这就叫做预处理。 ![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. 均衡集群操作
  • [技术干货] WeAutomate Assistant 助手2.17版本以及后续版本点击启动无反应,检查日志报错提示SQLError
    问题现象:助手安装路径下logs文件夹中agent.log日志文件,报错提示如下:可能原因:    助手异常退出,进程没有结束,重新启动新的进程,导致连接数据库冲突解决办法:1. 删除助手安装目录\agent目录中的db文件夹2. 打开任务管理器,结束WeAutomate Assistant助手全部进程与javaw.exe进程(也可使用cmd命令结束进程)3. 重新启动助手注:如以上操作仍无法解决问题,可通过华为云论坛进行提问
  • [交流吐槽] 第三章笔记
    # 第三章笔记 ## 任务管理 ### 任务管理基本概念 ![屏幕截图 2022-07-19 095909.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310313063857726.png) ### 任务状态 就绪(Ready):该任务在就绪列表中,只等待CPU 运行(Running):该任务正在执行 阻塞(Blocked):该任务不在就绪列表中,包含任务被挂起、被延时、任务正在等待信号量、读写队列或者等待读写事件等 退出态(Dead):该任务运行结束,等待系统回收资源 ### 任务相关概念 ![屏幕截图 2022-07-19 100355.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310340704306143.png) ### 任务调度机制 ![屏幕截图 2022-07-19 100910.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310364389785084.png) ### 实现任务管理 ![屏幕截图 2022-07-19 101034.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310387024922301.png) 删除某个任务:osThreadTerminate(os Threadld_t thread_id); 任务挂起:osThreadSuspend(os Threadld_t thread_id); 任务恢复:os ThreadResume(osThreadld_t thread_id); 创建任务接口: ![屏幕截图 2022-07-19 101327.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310405496618669.png) func:任务函数 argument:作为启动函数传递给任务函数的指针 attr:任务入口函数的参数列表 返回值:任务ID ## 软件定时器 ### 基本概念 硬件定时器受硬件的限制,不满足用户的需求,提供更多定时器,LiteOS操作系统提供软件定时器 ![屏幕截图 2022-07-19 103853.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310424438908216.png) ![屏幕截图 2022-07-19 104724.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310434097328091.png) ### 运作机制 ![屏幕截图 2022-07-19 104834.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310449852805341.png) ### 实现软件定时器创建 ![屏幕截图 2022-07-19 104933.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310487352485631.png) ![屏幕截图 2022-07-19 105216.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310495770350755.png) 可以创建单次或者是周期性定时器,如果是单次定时器,想要停止定时器,需要在运行期间停止,删除定时器任意时期都可以 ## 信号量 ### 概念 ![屏幕截图 2022-07-19 112327.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310525821114263.png) ![屏幕截图 2022-07-19 112354.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310539672775982.png) ### 运作机制 运作原理 ![屏幕截图 2022-07-19 162509.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310558273789193.png) ![屏幕截图 2022-07-19 162541.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310563658579032.png) 信号量运作示意图 ![屏幕截图 2022-07-19 162702.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310586044708842.png) 线程1、2、3、4都获取到了公共资源的任务数,这时线程5会被阻塞,当线程1完成并离开之后,线程5可以执行 ### 实现信号量 ![屏幕截图 2022-07-19 163137.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310603011329545.png) ### 信号量释放与信号量计数的关系 每次释放信号量时,信号量计数器的计数值会增加1,每申请一次信号量,计数值会减少一,能不能申请到,取决于计数值是否大于1 ## 事件管理 ### 概念 ![屏幕截图 2022-07-20 165156.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310679571535384.png) ### 运作机制 ![屏幕截图 2022-07-20 165326.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310706177404220.png) 事件2发生的时候,任务a都会被唤醒 b任务是在事件2和事件5都发生时才会被唤醒 即一个事件可以对应多个任务,或者是多个事件来对应一个任务 ### 实现事件功能 ![屏幕截图 2022-07-20 165629.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310725781856096.png) ## 互斥锁 ### 概念 ![JPCI82BC5OK{S~G0J}403AL.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310743772472451.png) ### 运作机制 ![屏幕截图 2022-07-20 170906.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310763568941772.png) 线程1,2都要访问公共资源,线程1访问之后,公共资源会上锁,线程2被挂起,也就是第一个公共资源只能被独占式访问,当线程1释放互斥锁时,线程2才能访问,确保同一时刻只有一个线程访问公共资源,以保证公共资源的完整性。 ### 实现互斥锁 ![屏幕截图 2022-07-20 170748.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310792847734015.png) ## 消息队列 ### 概念 ![屏幕截图 2022-07-20 172245.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310809118936670.png) ### 特性 ![屏幕截图 2022-07-20 172555.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310826209936903.png) ### 运作机制 ![屏幕截图 2022-07-20 172632.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310845568424408.png) 消息头head和消息尾tail表示单型队列,head表示队列中被占用消息的起始位置,tail表示队列中被空闲消息的起始位置 ![屏幕截图 2022-07-20 172849.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310861256616220.png) ### 实现消息队列 ![屏幕截图 2022-07-20 173025.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/20/1658310878281658012.png) ## 心得 代码不难理解,但是需要按照教学视频的步骤的学习
  • [其他] 简记:Gauss与Citus使用体验对比
    一、测试环境1.硬件环境1台   coordinator3台   worker/datanodeOS :     Centos 7.4CPU: 4U内存: 32G2.软件环境Gauss:版本:8.0集群规模:3节点*2进程  配置高可用存储类型:行存表Citus:版本:PG 14.1 + Citus 10.2.3集群规模:3节点*2进程  未配置高可用存储类型:行存表二、测试方法(TPCH标准测试集兼容性和性能测试)1.构造数据cd /srv/BigData/tar -zxvf TPCH.tar.gzcd TPCHtar -zxvf dbgen.tar.gzchmod 777 -R /srv/BigData/TPCHchown -R omm:wheel /srv/BigData/TPCHcd /srv/BigData/TPCH/dbgen./dbgen -s 102.建表Gauss外表:gsql -d postgres -p 25308 -f TPCH_CREATE_FOREIGN_TABLE_001.sql示例:drop FOREIGN table if exists orders_load;CREATE FOREIGN TABLE ORDERS_load(    O_ORDERKEY       BIGINT  , O_CUSTKEY        BIGINT  , O_ORDERSTATUS    CHAR(1)  , O_TOTALPRICE     DECIMAL(15,2)  , O_ORDERDATE      DATE  , O_ORDERPRIORITY  CHAR(15)  , O_CLERK          CHAR(15)  , O_SHIPPRIORITY   BIGINT  , O_COMMENT        VARCHAR(79))SERVER gsmpp_serverOPTIONS(location 'gsfs://xx.xx.xx.xx:xx/orders*',format 'text',delimiter '|',encoding 'utf8',mode 'Normal');分区表gsql -d postgres -p 25308 -f TPCH_CREATE_PARTITION_TABLE_001.sqldrop table if exists orders;CREATE TABLE ORDERS(    O_ORDERKEY       BIGINT NOT NULL  , O_CUSTKEY        BIGINT NOT NULL  , O_ORDERSTATUS    CHAR(1) NOT NULL  , O_TOTALPRICE     DECIMAL(15,2) NOT NULL  , O_ORDERDATE      DATE NOT NULL  , O_ORDERPRIORITY  CHAR(15) NOT NULL  , O_CLERK          CHAR(15) NOT NULL  , O_SHIPPRIORITY   BIGINT NOT NULL  , O_COMMENT        VARCHAR(79) NOT NULL  --, primary key (O_ORDERKEY))--with (orientation = column)distribute by hash(O_ORDERKEY)PARTITION BY RANGE(O_ORDERDATE)(    PARTITION O_ORDERDATE_1 VALUES LESS THAN('1993-01-01 00:00:00'),    PARTITION O_ORDERDATE_2 VALUES LESS THAN('1994-01-01 00:00:00'),    PARTITION O_ORDERDATE_3 VALUES LESS THAN('1995-01-01 00:00:00'),    PARTITION O_ORDERDATE_4 VALUES LESS THAN('1996-01-01 00:00:00'),    PARTITION O_ORDERDATE_5 VALUES LESS THAN('1997-01-01 00:00:00'),    PARTITION O_ORDERDATE_6 VALUES LESS THAN('1998-01-01 00:00:00'),    PARTITION O_ORDERDATE_7 VALUES LESS THAN('1999-01-01 00:00:00'));Citus分区表建表语句示例drop table if exists orders;CREATE TABLE ORDERS(    O_ORDERKEY       BIGINT NOT NULL  , O_CUSTKEY        BIGINT NOT NULL  , O_ORDERSTATUS    CHAR(1) NOT NULL  , O_TOTALPRICE     DECIMAL(15,2) NOT NULL  , O_ORDERDATE      DATE NOT NULL  , O_ORDERPRIORITY  CHAR(15) NOT NULL  , O_CLERK          CHAR(15) NOT NULL  , O_SHIPPRIORITY   BIGINT NOT NULL  , O_COMMENT        VARCHAR(79) NOT NULL  --, primary key (O_ORDERKEY))PARTITION BY RANGE(O_ORDERDATE);CREATE TABLE ORDERS_1   PARTITION OF ORDERS    FOR VALUES from ('1992-01-01 00:00:00') to ('1993-01-01 00:00:00' );CREATE TABLE ORDERS_2   PARTITION OF ORDERS    FOR VALUES from ('1993-01-01 00:00:00') to ('1994-01-01 00:00:00' );CREATE TABLE ORDERS_3   PARTITION OF ORDERS    FOR VALUES from ('1994-01-01 00:00:00') to ('1995-01-01 00:00:00' );CREATE TABLE ORDERS_4   PARTITION OF ORDERS    FOR VALUES from ('1995-01-01 00:00:00') to ('1996-01-01 00:00:00' );CREATE TABLE ORDERS_5   PARTITION OF ORDERS    FOR VALUES from ('1996-01-01 00:00:00') to ('1997-01-01 00:00:00' );CREATE TABLE ORDERS_6   PARTITION OF ORDERS    FOR VALUES from ('1997-01-01 00:00:00') to ('1998-01-01 00:00:00' );CREATE TABLE ORDERS_7   PARTITION OF ORDERS    FOR VALUES from ('1998-01-01 00:00:00') to ('1999-01-01 00:00:00' );3.导入数据Gauss:通过gds导入(类似pg_fdw)gds -d /srv/BigData/TPCH/dbgen -p xx.xx.xx.xx:5001 -H 0.0.0.0/0 -l /srv/BigData/TPCH/gds_log.txt -D入库语句示例:insert into  orders   select * from  orders_load;analyze orders;--分区表:orders--外表:orders_loadCitus:通过copy语句导入copy orders from '/srv/BigData/TPCH/dbgen/orders.tbl' with (format 'text',delimiter '|',encoding 'utf8');SELECT create_distributed_table('orders', 'o_orderkey');analyze orders;4.进行测试测试案例选择22个场景,以下提供3个作为参考测试语句示例1:select        l_returnflag,        l_linestatus,        sum(l_quantity) as sum_qty,        sum(l_extendedprice) as sum_base_price,        sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,        sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)/1000) as sum_charge, --add /1000        avg(l_quantity) as avg_qty,        avg(l_extendedprice) as avg_price,        avg(l_discount) as avg_disc,        count(*) as count_orderfrom        lineitemwhere        l_shipdate <= date '1998-12-01' - interval '3 day'group by        l_returnflag,        l_linestatusorder by        l_returnflag,        l_linestatus;测试语句示例2:select        s_acctbal,        s_name,        n_name,        p_partkey,        p_mfgr,        s_address,        s_phone,        s_commentfrom        part,        supplier,        partsupp,        nation,        region,        (                select                        ps_partkey as temp_ps_partkey, min(ps_supplycost) as temp_min_ps_supplycost                from                        partsupp,                        supplier,                        nation,                        region                where                        s_suppkey = ps_suppkey                        and s_nationkey = n_nationkey                        and n_regionkey = r_regionkey                        and r_name = 'EUROPE'            group by ps_partkey        ) as tempwhere    p_partkey = temp_ps_partkey        and p_partkey = ps_partkey        and s_suppkey = ps_suppkey        and p_size = 15        and p_type like '%BRASS'        and s_nationkey = n_nationkey        and n_regionkey = r_regionkey        and r_name = 'EUROPE'        and ps_supplycost = temp_min_ps_supplycostorder by        s_acctbal desc,        n_name,        s_name,        p_partkeylimit 100;测试语句示例3:select        sum(l_extendedprice) / 7.0 as avg_yearlyfrom        lineitem,        part,        (select            l_partkey as temp_l_partkey, 0.2 * avg(l_quantity) as temp_avg         from                lineitem         group by l_partkey        ) as tempwhere        p_partkey = l_partkey        and p_brand = 'Brand#23'        and p_container = 'MED BOX'        and l_quantity < temp_avg        and p_partkey = temp_l_partkey;三、测试结果1.空间利用率比较GaussCitus2.语法兼容性比较gauss支持声明式创建分区表,建表指定分布列,自动发送数据到datanodeCREATE TABLE ORDERS(    O_ORDERKEY       BIGINT NOT NULL  , O_CUSTKEY        BIGINT NOT NULL …)distribute by hash(O_ORDERKEY)PARTITION BY RANGE(O_ORDERDATE)(    PARTITION O_ORDERDATE_1 VALUES LESS THAN('1993-01-01 00:00:00'),    PARTITION O_ORDERDATE_2 VALUES LESS THAN('1994-01-01 00:00:00'),...);citus支持声明式创建分区表,但建表与建分区需独立执行,需手动分发数据CREATE TABLE ORDERS(    O_ORDERKEY       BIGINT NOT NULL  , O_CUSTKEY        BIGINT NOT NULL …)PARTITION BY RANGE(O_ORDERDATE);CREATE TABLE ORDERS_1   PARTITION OF ORDERS    FOR VALUES from ('1992-01-01 00:00:00') to ('1993-01-01 00:00:00' );CREATE TABLE ORDERS_2   PARTITION OF ORDERS    FOR VALUES from ('1993-01-01 00:00:00') to ('1994-01-01 00:00:00' );SELECT create_distributed_table('ORDERS', 'o_orderkey');3.join支持Gauss:支持(1)分布列进行join(2)一个分布列,一个不是分布列join(3)两张表都不是分布列joinCitus:支持(1)分布列进行join(2)一个分布列,一个不是分布列join,需要Set citus.enable_repartition_joins to on,否则报错(3)两张表都不是分布列join,不支持外连接,报错如下4.运维管理Gauss有cm(cluster manager)对进程故障检测,进程自拉起,主备高可用,集群拓扑等功能提供命令与视图Citus对worker管理提供了sql支持,高可用依赖postgres实现,有待进一步了解5.性能比较关于性能差异的猜想:a. gauss配置了主备同步,可能会牺牲部分性能保障数据安全b. citus除了实例级别的数据切分,还有数据切片机制,能更好地并行计算四、总结citus作为pg的一个插件,跟随pg主线演进,可以结合社区新特性,性能不俗,能够弹性扩展;但是作为插件也天然存在局限性,例如对join的支持,与pg运维管理相关功能的配合,产品对使用者的能力要求比较高。gauss集群建设更加完善,提供了cm(cluster manager),om(operation manager)以及更上层的维护功能,提供集群高可用,在分布式场景能够完全兼容单机sql语法,对使用者更加友好。五、遗留了解Citus分片切分的粒度与机制,了解分片是否会产生大量小文件问题了解citus分布式计划实现原理,了解citus对需要先redistribute进行join限制的原因
  • [问题求助] 【appcube】脚本中如何实现多线程?
    脚本中如何实现多线程?
  • [互动交流] 【产品】【EXCEL功能】重新安装后无法正常使用控件
    【功能模块】 升级到 3.0.3 以后,发现一些问题, 卸载后,又装了3.0.0, 发现无法正常使用。请帮助。 【操作步骤&问题现象】1、关闭EXCEL 控件发生错误。 日志如下。 2、【截图信息】【日志信息】(可选,上传日志内容或者附件)NOK][2022-07-14 10:09:08]完成执行[原子命令] [playbackScript.xml 第4行] 结束Excel进程 [结束Excel进程] [NOK] [RobotUncaughtError] [系统执行错误: 发生了未捕获异常,详情:ImportError(\&quot;cannot import name &apos;EXCEL_SUFFIX&apos; from &apos;com.huawei.antrobot.settings.robot_settings&apos; (D:\\\\Program\\\\Studio\\\\Robot\\\\com\\\\huawei\\\\antrobot\\\\settings\\\\robot_settings.pyc)\&quot;)]
  • [DevKit] DevKit第三次作业-《锁长期等待》大作业
    锁长期等待---- pthread_mutex_long.c一、作业题目通过性能分析工具找出程序中加锁范围不合理的地方并解决二、操作前提在鲲鹏社区申请远程实验室,操作系统选择OpenEulerhttps://www.hikunpeng.com/zh/developer/devkit三、准备工作1、打开网页,找到测试程序 :pthread_mutex_long.c ,复制里面的内容到本地同名的文件里。注意:不要直接下载这个文件,只需要复制网页里面的内容,在本地vim新建一个pthread_mutex_long.c,然后把网页里面的内容拷贝进去。即可https://github.com/kunp e ngcompute/devkitdemo/blob/main/Hyper_tuner/testdemo/lock/pthread_mutex_long.c vim命令新建一个pthread_mutex_long.c然后拷贝网页里面的代码到此文件后,按:wq! 保存退出。2、授权文件:chmod 777 pthread_mutex_long.c3、编译程序:gcc -g pthread_mutex_long.c -o pthread_mutex_long -lpthread -lm && chmod 777 pthread_mutex_long4、绑核启动:taskset -c 0-1 ./pthread_mutex_long四、作业要求1、操作步骤截图越细越好,每张截图最好加上说明。2、通过性能分析工具发现环境中有性能问题的目标程序。3、发现可调优的目标程序后,修改代码,重新使用性能分析工具进行分析,相关指标有变化说明调优成功。五、操作步骤截图关键截图一:全景分析任务结果-性能tab通过cpu的相关指标看出cpu0 或 cpu1的 用户态使用率比较高。关键截图二:进程/线程性能分析任务结果-总览总览页签下查看各进程的CPU使用情况,发现启动命令为 ./pthread_mutex_long 的进程的用户态使用率高,这个正好也是我们启动的测试程序。关键截图三:资源调度分析任务结果-总览关键截图四:资源分析任务结果-进程/线程调度发现在采样期间两个线程之间的调度没有交集,没有平衡的相互交替运行,我们可以推断两个线程可能在抢占某个资源关键截图五:热点函数分析任务结果-总览测试程序的热点函数是 Func关键截图六:热点函数分析任务结果-热点函数源码通过对热点函数的源码分析,发现其中有一段业务逻辑并不涉及并发的场景,并且这块业务逻辑是Func函数中热度最高的,完全可以移到锁的范围之外去。注意:此时查看源码文件,需要执行1亿次循环,还要做N=100000次的平方根,所以就进入了耗时的循环。所以我们将float 字段迁移到循环的外面即可。关键截图七:代码优化前后优化前:优化后:然后重新编译和绑核启动,编译:gcc -g pthread_mutex_long.c -o pthread_mutex_long -lpthread -lm && chmod 777 pthread_mutex_long绑核启动:taskset -c 0-1 ./pthread_mutex_long关键截图八:代码优化后重新启动,资源分析任务结果-进程/线程调度测试程序的两个线程会平稳的进行,不会存在长时间的等待。作业结束!完毕!
总条数:813 到第 页
上滑加载中