仓颉之并发编程的速度激情

1 概述

1.1 案例介绍

仓颉编程语言作为一款面向全场景应用开发的现代编程语言,通过现代语言特性的集成、全方位的编译优化和运行时实现、以及开箱即用的 IDE工具链支持,为开发者打造友好开发体验和卓越程序性能。

案例结合代码体验,帮助大家更直观的学习仓颉语言中并发编程知识。

1.2 适用对象

  • 个人开发者
  • 高校学生

1.3 案例时间

本案例总时长预计60分钟。

1.4 案例流程

abf6ec3409dff70ce74ed05ed269792a.png

说明:

  1. 进入华为开发者空间,登录云主机;
  2. 使用CodeArts IDE for Cangjie编程和运行仓颉代码。。

1.5 资源总览

资源名称规格单价(元)时长(分钟)
开发者空间 - 云主机鲲鹏通用计算增强型 kc2 | 4vCPUs | 8G | Ubuntu免费60

仓颉之并发编程的速度激情 👈👈👈体验完整版案例,点击这里。

2 环境准备

2.1 开发者空间配置

面向广大开发者群体,华为开发者空间提供一个随时访问的“开发桌面云主机”、丰富的“预配置工具集合”和灵活使用的“场景化资源池”,开发者开箱即用,快速体验华为根技术和资源。

领取云主机后可以直接进入华为开发者空间工作台界面,点击打开云主机 > 进入桌面连接云主机。没有领取在开发者空间根据指引领取配置云主机即可,云主机配置参考1.5资源总览。

a1aae6ff53aac98855ef597dd6899967.png

552fc96c3b58a06e294e4a760ae719e3.PNG

点击桌面CodeArts IDE for Cangjie,打开编辑器,点击新建工程,名称demo,其他保持默认配置,点击创建。

产物类型说明:

  • executable,可执行文件;
  • static,静态库,是一组预先编译好的目标文件的集合;
  • dynamic,动态库,是一种在程序运行时才被加载到内存中的库文件,多个程序共享一个动态库副本,而不是像静态库那样每个程序都包含一份完整的副本。

28acbca9146a8a6aacbfdd4f6ac3791b.png

创建完成后,打开src/main.cj,点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

8aaa6cc304676cf77e05cbf197af18f6.png

后续文档中的代码验证均可以替换main.cj中的代码(package demo包路径保留)后执行,demo是项目名称,与创建项目时设置的保持一致。

至此,云主机环境配置完毕。

3 并发编程

3.1 并发概述

并发编程是现代编程语言中不可或缺的特性,仓颉编程语言提供抢占式的线程模型作为并发编程机制。在谈及编程语言和线程时,线程可以细化为两种不同概念:语言线程 和 native 线程。

  • 语言线程:是编程语言中并发模型的基本执行单位,语言线程的目的是屏蔽底层实现细节。

  • native线程:指语言实现中所使用到的线程(一般是操作系统线程),他们作为语言线程的具体实现载体。

    仓颉线程本质上是用户态的轻量级线程,每个仓颉线程都受到底层 native 线程的调度执行,并且多个仓颉线程可以由一个 native 线程执行。每个 native 线程会不断地选择一个就绪的仓颉线程完成执行,如果仓颉线程在执行过程中发生阻塞(例如等待互斥锁的释放),那么 native 线程会将当前的仓颉线程挂起,并继续选择下一个就绪的仓颉线程。发生阻塞的仓颉线程在重新就绪后会继续被 native 线程调度执行。

    在大多数情况下,开发者只需要面向仓颉线程进行并发编程而不需要考虑这些细节。但在进行跨语言编程时,开发者需要谨慎调用可能发生阻塞的 foreign 函数,例如 IO 相关的操作系统调用等。

    例如:下列示例代码中的新线程会调用 foreign 函数 socket_read。在程序运行过程中,某一 native 线程将调度并执行该仓颉线程,在进入到 foreign 函数中后,系统调用会直接阻塞当前 native 线程直到函数执行完成。native 线程在阻塞期间将无法调度其他仓颉线程来执行,这会降低程序执行的吞吐量。

foreign socket_read(sock: Int64): CPointer<Int8>
let fut = spawn {
    let sock: Int64 = ...
    let ptr = socket_read(sock)
}

3.2 创建线程

当开发者希望并发执行某一段代码时,只需创建一个仓颉线程即可。要创建一个新的仓颉线程,可以使用关键字 spawn 并传递一个无形参的 lambda 表达式,该 lambda 表达式即为在新线程中执行的代码。

例如在主线程中新建一个线程,两线程分别打印文本。

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
main(): Int64 {
    // 创建新线程
    spawn { =>
        println("New thread before sleeping")	// 新线程打印文本
        sleep(100 * Duration.millisecond) // 新线程睡眠100ms.
        println("New thread after sleeping")
    }
    // 主线程打印文本
    println("Main thread")
    return 0
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

293459d5d4029d2aefa7257488c3efad.png

(* 注意:在上面的例子中,新线程会在主线程结束时一起停止,无论这个新线程是否已完成运行,所以每次运行的结果可能不一样)

sleep()函数:可以让当前线程睡眠指定的时间后再恢复执行,其时间由指定的 Duration 类型决定。函数原型为:

func sleep(dur: Duration): Unit

(* 注意:Duration.Zero表示 0 纳秒时间间隔的 Duration 实例,如果 dur <= Duration.Zero, 那么当前线程只会让出执行资源,并不会进入睡眠)

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
main(): Int64 {
    println("Hello")
    // Duration.second表示1秒时间间隔的Duration实例
    sleep(3 * Duration.second)  // 让主线程睡眠 3s. 
    println("World")
    return 0
} 

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

e181ef25dc7c9ea23daa39ba78d8c297.png

3.3 访问线程

3.3.1 使用 Future<T> 等待线程结束

在上面的例子中,新创建的线程会由于主线程结束而提前结束,在缺乏顺序保证的情况下,甚至可能会出现新创建的线程还来不及得到执行就退出了。可以通过 spawn 表达式的返回值,来等待线程执行结束。

spawn 表达式的返回类型是 Future<T>,其中 T 是类型变元,其类型与 lambda 表达式的返回类型一致。当调用 Future<T> 的 get() 成员函数时,它将等待它的线程执行完成。

下方示例代码演示了如何使用 Future<T> 在 main 中等待新创建的线程执行完成:

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
main(): Int64 {
    let fut: Future<Unit> = spawn { =>
        println("New thread before sleeping")
        sleep(3 * Duration.second) // 睡眠3s.
        println("New thread after sleeping")
    }
    fut.get() // 等待该线程执行完成.
    println("Main thread")
    return 0
}

get()调用后的代码会等待调用线程执行完成后再执行。

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

717c20d03665250a93698e4f913dc5a0.png

3.3.2 使用 Future<T> 获取线程返回值

Future<T> 除了可以用于阻塞等待线程执行结束以外,还可以获取线程执行的结果。下面我们来看一下它提供的具体成员函数:

1. get(): T:阻塞等待线程执行结束,并返回执行结果,如果该线程已经结束,则直接返回执行结果,示例如下:

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
main(): Int64 {
    let fut: Future<Int64> = spawn {
        sleep(Duration.second) // 睡眠 1s.
        return 1
    }
    try {
        // 等待线程fut执行完成并获取执行结果
        let res: Int64 = fut.get()
        println("result = ${res}")
    } catch (_) {
        println("线程执行出现异常")
    }
    return 0
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

20b3bd47aa5a7c11b05a47699f941edb.png

2. get(ns: Int64): Option<T>:阻塞等待该 Future<T> 所代表的线程执行结束,并返回执行结果,当到达超时时间 ns 时,如果该线程还没有执行结束,将会返回 Option<T>.None。如果 ns <= 0,其行为与 get() 相同,示例如下:

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
main(): Int64 {
    let fut = spawn {
        sleep(Duration.second) // 睡眠 1s.
        return 1
    }
    // 等待fut线程执行完成并获取结果, 等待超时时间 1ms.
    let res: Option<Int64> = fut.get(1000 * 1000)
    match (res) {
        case Some(val) => println("result = ${val}")
        case None => println("等待fut执行完成超时")
    }
    return 0
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

254ddce8bd275e1d6426fbba609e994e.png

3.3.3 访问线程属性

每个 Future<T> 对象都有一个对应的仓颉线程,以 Thread 对象为表示。Thread 类主要被用于访问线程的属性信息,例如线程标识等。需要注意的是,Thread 无法直接被实例化构造对象,仅能从 Future<T> 的 thread 成员属性获取对应的 Thread 对象,或是通过 Thread 的静态成员属性 currentThread 得到当前正在执行线程对应的 Thread 对象。

例如,在创建新线程后分别通过两种方式获取线程标识。由于主线程和新线程获取的是同一个 Thread 对象,所以他们能够打印出相同的线程标识。

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
main(): Unit {
    let fut = spawn {
        println("Current thread id: ${Thread.currentThread.id}")
    }
    println("New thread id: ${fut.thread.id}")
    fut.get()
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

c0e06cccb74f485b7334f0435151a283.png

3.4 终止线程

可以通过 Future<T> 的 cancel() 方法向对应的线程发送终止请求,该方法不会停止线程执行。开发者需要使用 Thread 的 hasPendingCancellation 属性来检查线程是否存在终止请求。

一般而言,如果线程存在终止请求,那么开发者可以实施相应的线程终止逻辑。因此,如何终止线程都交由开发者自行处理,如果开发者忽略终止请求,那么线程继续执行直到正常结束。

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.SyncCounter
main(): Unit {
    let syncCounter = SyncCounter(1)
    let fut = spawn {
        syncCounter.waitUntilZero()  // 等待倒数计数器变为0 
        // 检查取消请求, 自定义取消逻辑
        if (Thread.currentThread.hasPendingCancellation) {
            println("cancelled")
            return
        }
        println("hello")
    }
    fut.cancel()    // 发送取消请求
    syncCounter.dec() // 唤醒所有等待的线程
    fut.get() // 确保fut线程执行完成
}

类型 SyncCounter 提供倒数计数器功能,线程可以等待计数器变为零。

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

c5f56cae28a3b2ce2acefa213ac3a45a.png

3.5 同步机制

在并发编程中,如果缺少同步机制来保护多个线程共享的变量,很容易会出现数据竞争问题(data race)。

仓颉编程语言提供三种常见的同步机制来确保数据的线程安全:原子操作、互斥锁和条件变量。

3.5.1 原子操作 Atomic

仓颉提供整数类型、Bool 类型和引用类型的原子操作。

其中整数类型包括: Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32、UInt64。整数类型的原子操作支持基本的读写、交换以及算术运算操作:

操作功能
load读取
store写入
swap交换,返回交换前的值
compareAndSwap比较再交换,交换成功返回 true,否则返回 false
fetchAdd加法,返回执行加操作之前的值
fetchSub减法,返回执行减操作之前的值
fetchAnd与,返回执行与操作之前的值
fetchOr或,返回执行或操作之前的值
fetchXor异或,返回执行异或操作之前的值

(* 注意:交换操作和算术操作的返回值是修改前的值;compareAndSwap 是判断当前原子变量的值是否等于 old 值,如果等于,则使用 new 值替换;否则不替换)

例如,在多线程程序中,使用原子操作实现计数:

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
import std.collection.*
let count = AtomicInt64(0)
main(): Int64 {
    let list = ArrayList<Future<Int64>>()
    // 创建1000个线程
    for (_ in 0..1000) {
        let fut = spawn {
            sleep(Duration.millisecond) // 睡眠 1ms.
            count.fetchAdd(1)
        }
        list.append(fut)
    }
    // 等待所有线程执行完成
    for (f in list) {
        f.get()
    }
    let val = count.load()
    println("count = ${val}")
    return 0
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

467fbf891566aeb26834f9e84d07ce7e.png

Bool 类型和引用类型的原子操作只提供读写和交换操作:

操作功能
load读取
store写入
swap交换,返回交换前的值
compareAndSwap比较再交换,相同则交换成功返回 true,否则返回 false

原子引用类型是 AtomicReference,以下是使用 Bool 类型、引用类型原子操作的一些正确示例:

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
class A {}
main() {
    var obj = AtomicBool(true)
    var x1 = obj.load() // x1: true, 类型是 Bool
    println(x1)
    var t1 = A()
    var obj2 = AtomicReference(t1)
    var x2 = obj2.load() // x2 和 t1 是相同的对象
    // 相同的对象, 交换成功, y1: true
    var y1 = obj2.compareAndSwap(x2, t1) 
    println(y1)
    var t2 = A()
    // 不是相同的对象, 交换失败, y2: false
    var y2 = obj2.compareAndSwap(t2, A()) 
    println(y2)
    y2 = obj2.compareAndSwap(t1, A()) // 交换成功, y2: true
    println(y2)
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

1deceb831ee693e1bb1757c93fcabaa9.png

3.5.2 可重入互斥锁 ReentrantMutex

可重入互斥锁的作用是对临界区加以保护,使得任意时刻最多只有一个线程能够执行临界区的代码。当一个线程试图获取一个已被其他线程持有的锁时,该线程会被阻塞,直到锁被释放,该线程才会被唤醒,可重入是指线程获取该锁后可再次获得该锁。

ReentrantMutex 是仓颉内置的互斥锁。使用可重入互斥锁时,必须牢记两条规则:

  • 在访问共享数据之前,必须尝试获取锁;
  • 处理完共享数据后,必须进行解锁,以便其他线程可以获得锁。

ReentrantMutex 提供的主要成员函数如下:

public open class ReentrantMutex {
    // 创建可重入互斥锁
    public init()
    // 锁定互斥体,如果互斥体已被锁定,则阻塞
    public func lock(): Unit
    // 解锁互斥体
    // 如果有其他线程阻塞在此锁上,那么唤醒他们中的一个
    public func unlock(): Unit
    // 尝试锁定互斥体
    // 如果互斥体已被锁定,则返回 false;反之,则锁定互斥体并返回 true
    public func tryLock(): Bool
}

例如,使用 ReentrantMutex 来保护对全局共享变量 count 的访问,对 count 的操作即属于临界区:

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
import std.collection.*
var count: Int64 = 0
let mtx = ReentrantMutex()
main(): Int64 {
    let list = ArrayList<Future<Unit>>()
    // 创建100个线程.
    for (_ in 0..1000) {
        let fut = spawn {
            sleep(Duration.millisecond) // 睡眠 1ms.
            mtx.lock() // 上锁
            count++	// 临界区代码
            mtx.unlock()	// 释放锁
        }
        list.append(fut)
    }
    // 等待所有线程完成
    for (f in list) {
        f.get()
    }
    println("count = ${count}")
    return 0
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

bc6f8df5f99f7bd95b13cb6fe8987a19.png

3.5.3 Monitor

Monitor 是一个内置的数据结构,它绑定了互斥锁和单个与之相关的条件变量(也就是等待队列)。Monitor 可以使线程阻塞并等待来自另一个线程的信号以恢复执行。这是一种利用共享变量进行线程同步的机制,主要提供如下方法:

public class Monitor <: ReentrantMutex {
    // 通过默认构造函数创建 Monitor
    public init()
    // 当前线程挂起,直到对应的 notify 函数被调用,或者挂起时间超过 timeout
    public func wait(timeout!: Duration = Duration.Max): Bool
    // 唤醒一个等待在该 Montior 上的线程
    public func notify(): Unit
    // 唤醒所有等待在该 Montior 上的线程
    public func notifyAll(): Unit
}

下面是一个使用Monitor实现互斥锁的示例

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.time.*
var mon = Monitor()
var flag: Bool = true
main(): Int64 {
    let fut = spawn {
        mon.lock()		// 上锁
        while (flag) {
            println("New thread: before wait")
            mon.wait()
            println("New thread: after wait")
        }
        mon.unlock()	// 解锁
    }
    // 睡眠 10ms, 确保新线程执行完成.
    sleep(10 * Duration.millisecond)
    mon.lock()
    println("Main thread: set flag")
    flag = false
    mon.unlock()
    mon.lock()
    println("Main thread: notify")
    mon.notifyAll()
    mon.unlock()
    // 等待新线程执行完成
    fut.get()
    return 0
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

8e0ada7e49aa1cca995873ceb984b4ec.png

3.5.4 MultiConditionMonitor

MultiConditionMonitor 是一个内置的数据结构,它绑定了互斥锁和一组与之相关的动态创建的条件变量。该类应仅当在 Monitor 类不足以满足复杂的线程间同步的场景下使用。主要提供如下方法:

public class MultiConditionMonitor <: ReentrantMutex {
   // 通过默认构造函数创建 MultiConditionMonitor
   init()
   // 创建一个与该 Monitor 相关的 ConditionID
   // 可能被用来实现 “单互斥体多等待队列” 的并发原语
   // 如果当前线程没有持有该互斥体,抛出异常
   func newCondition(): ConditionID
   // 当前线程挂起,直到对应的 notify 函数被调用
   func wait(id: ConditionID, timeout!: Duration = Duration.Max): Bool
   // 唤醒等待在所指定的条件变量的线程(如果有)
   func notify(id: ConditionID): Unit
   // 唤醒所有等待在所指定的条件变量的线程(如果有)
   func notifyAll(id: ConditionID): Unit
}

(* 初始化时,MultiConditionMonitor 没有与之相关的 ConditionID 实例。每次调用 newCondition 都会将创建一个新的条件变量并与当前对象关联)

例如,使用 MultiConditionMonitor 去实现一个长度固定的有界 FIFO(先进先出) 队列,当队列为空,get() 会被阻塞;当队列满了时,put() 会被阻塞。

import std.sync.*
class BoundedQueue {
    // 创建一个 MultiConditionMonitor, 两个Conditions.
    let m: MultiConditionMonitor = MultiConditionMonitor()
    var notFull: ConditionID
    var notEmpty: ConditionID
    var count: Int64 // 整数缓冲区
    var head: Int64  // 写入索引
    var tail: Int64  // 读取索引 
    // 队列长度100
    let items: Array<Object> = Array<Object>(100, {i => Object()})
    init() {
        count = 0
        head = 0
        tail = 0
        synchronized(m) {
          notFull  = m.newCondition()
          notEmpty = m.newCondition()
        }
    }
    // 插入一个对象,如果队列已满,则使当前线程阻塞。
    public func put(x: Object) {
        // 加互斥锁
        synchronized(m) {
          while (count == 100) {
            // 如果队列已满, 等待 "queue notFull" 事件触发
            m.wait(notFull)
          }
          items[head] = x
          head++
          if (head == 100) {
            head = 0
          }
          count++
          // 已经插入了一个对象,并且当前队列不再是空的,
          // 因此唤醒之前由于队列是空的而被get()阻塞的线程
          m.notify(notEmpty)
        } // 释放互斥锁
    }
    // 如果队列为空,则弹出一个对象,并使当前线程阻塞
    public func get(): Object {
        // 加互斥锁
        synchronized(m) {
          while (count == 0) {
            // 如果队列为空, 等待 "queue notEmpty" 事件触发
            m.wait(notEmpty)
          }
          let x: Object = items[tail]
          tail++
          if (tail == 100) {
            tail = 0
          }
          count--
          // 弹出一个对象,而当前队列不再满,
          // 因此唤醒之前由于队列已满而被put()阻塞的线程
          m.notify(notFull)
          return x
        } // 释放互斥锁
    }
}
3.5.5 synchronized 关键字

互斥锁 ReentrantMutex 提供了一种便利灵活的加锁的方式,同时因为它的灵活性,也可能引起忘了解锁,或者在持有互斥锁的情况下抛出异常不能自动释放持有的锁的问题。因此,仓颉编程语言提供一个 synchronized 关键字,搭配 ReentrantMutex 一起使用,可以在其后跟随的作用域内自动进行加锁解锁操作,用来解决类似的问题。

注意:一个线程在进入 synchronized 修饰的代码块之前,会自动获取 ReentrantMutex 实例对应的锁,如果无法获取锁,则当前线程被阻塞。而线程在退出 synchronized 修饰的代码块之前,会自动释放该 ReentrantMutex 实例的锁,如通过控制转移表达式(如 break、continue、return、throw)跳出 synchronized 代码块。

使用 synchronized 关键字来保护共享数据可参考:

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

import std.sync.*
import std.collection.*
var count: Int64 = 0
var mtx: ReentrantMutex = ReentrantMutex()
main(): Int64 {
    let list = ArrayList<Future<Unit>>()
    for (_ in 0..10) {
        let fut = spawn {
            while (true) {
                // 使用 synchronized(mtx), 替换mtx.lock() 和 mtx.unlock().
                synchronized(mtx) {
                    count = count + 1
                    break
                    // 由于break跳出while循环,包括synchronized 代码块,
                    // 所以新线程中以下打印语句不会执行
                    println("in thread")
                }
            }
        }
        list.append(fut)
    }
    // 等待所有线程执行完成
    for (f in list) {
        f.get()
    }
    synchronized(mtx) {
        println("in main, count = ${count}")
    }
    return 0
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

aa2b9fbcdca518f45dd0cdf567621570.png

3.5.6 线程局部变量 ThreadLocal

使用 core 包中的 ThreadLocal 可以创建并使用线程局部变量,使用ThreadLocal实际是一种以空间换时间的做法,每一个线程都有它独立的一个存储空间来保存这些线程局部变量,因此,在每个线程可以安全地访问他们各自的线程局部变量,而不需要等待其他线程释放锁。主要提供如下方法:

public class ThreadLocal<T> {
    // 构造一个携带空值的仓颉线程局部变量
    public init()
    // 获得仓颉线程局部变量的值,如果值不存在,则返回 Option<T>.None
    public func get(): Option<T>
    // 通过 value 设置仓颉线程局部变量的值
    public func set(value: Option<T>): Unit
}

例如,两个线程通过 ThreadLocal类来创建并使用各自线程的局部变量:

Step1:复制以下代码,替换main.cj文件中的代码。(保留package)

main(): Int64 {
    let tl = ThreadLocal<Int64>()
    let fut1 = spawn {
        tl.set(123)
        println("tl in spawn1 = ${tl.get().getOrThrow()}")
    }
    let fut2 = spawn {
        tl.set(456)
        println("tl in spawn2 = ${tl.get().getOrThrow()}")
    }
    fut1.get()
    fut2.get()
    0
}

Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。

7999b0457d5d06c76c0d201487b3f2d4.png

至此,仓颉语言中并发编程知识内容介绍告一段落。

如果想了解更多仓颉编程语言知识可以访问: https://cangjie-lang.cn/

相关推荐

鸿蒙案例上新啦 | 购物、听歌、健身:15个模板带你一键激活社交娱乐

页面层级如何组织、跨页共享状态如何同步、列表与详情及个人中心如何形成稳定闭环。鸿蒙开发实践中心将这些高频消费与互动场景沉淀为可参考的标准模板,提供从业务架构到界面实现的快捷实践入口。更多实战案例欢迎前往鸿蒙开发实践中心案例中心查看并开启云端体验。

华为云官方博客 354

鸿蒙案例上新啦 | 出门打车、到家上课:25个模板带你一屏打通日常生活

如何合理拆分页面组件、如何建立单一可信的状态源、如何让用户在每一步操作中都获得明确反馈。鸿蒙开发实践中心以标准化的应用与元服务模板为入口,将清晰的业务架构与可复用的代码结构直观呈现。不妨立即挑选一个感兴趣的方向进入云端环境,从页面布局、组件封装、状态管理到路由组织,亲手跑通你的鸿蒙实战项目。

华为云官方博客 335

LakeAgent:DWS数据仓库的第二大脑,告别 “翻译式运维”,迈入智能自治时代

和普通 SQL 改写工具不同,LakeAgent 自动做多方案代价对比,主动规避负优化陷阱:补充分区键过滤实现剪枝,扫描分区由 37 收敛至 1,执行代价从 758.28 降至 113.74;未来的数据仓库,将不止能算、能存,更能看懂自己、诊断自己、理解自己。长久以来,数仓的智能都依附于人:信号需要人解读,瓶颈需要人研判,经验困在个体里,无法随底座永续生长。CPU 高、CPU 告警、CPU 诊断、CPU 使用率过高、CPU 满。语法、错误码、计费、性能调优、最佳实践、故障处置等全场景内容。

华为云官方博客 563

如何使用RDS Supabase后端BaaS能力:文件存储(File Storage)

• product-images(公开桶,允许公共读取):策略名称"Public read for product-images",允许SELECT,目标角色anon,策略定义bucket_id = 'product-images'。上传/删除需另配INSERT/DELETE策略,详见SQL。• product-specs(私有桶,仅认证用户访问自己的文件夹):建议拆为上传、读取、删除三条策略,策略定义均为 bucket_id = 'product-specs' 且仅限本人文件夹(详见下文 SQL)。

华为云官方博客 483

如何使用RDS Supabase后端BaaS能力:实时订阅与推送(Realtime)

Realtime 把数据库的每次变更同步到客户端:表里增、删、改一行,订阅者无需刷新页面或主动请求即可秒级收到。三大能力各司其职:• Postgres Changes — 监听表的 INSERT/UPDATE/DELETE,变化即通知• Broadcast — 客户端之间直接互发,适合瞬时通知、协同编辑• Presence — 跟踪频道里“谁在线”典型场景有实时数据展示(订单、库存、监控指标即时刷新)、即时通讯(消息收发、在线名单、“对方正在输入”)、协同编辑与实时通知。

华为云官方博客 473

AI Agent如何通过MCP连接到华为云RDS Supabase应用平台

Schema元数据列出所有可访问的表(按schema分组)。Schema元数据列出表的列及元数据(类型/默认值/约束)。Schema元数据列出所有约束(PK/FK/UNIQUE/CHECK/EXCLUDE)。可按schema/表/类型过滤。Schema元数据列出所有外键关系。可按schema/表过滤。Schema元数据列出所有索引及定义和大小。可按schema/表过滤。Schema元数据列出已应用的数据库迁移记录。Schema元数据列出已安装的PostgreSQL扩展。

华为云官方博客 480

华为大数据Flink让每一 CU 算力,跑出开源 5.74 倍的性能

1. 开源Apache Flink 已成为业界流计算事实标准,但在极端重负载场景下存在性能瓶颈。2. 华为大数据Flink通过多项自研深度优化,在执行计划、算子执行、状态存储三个层面构建协同的优化体系。3. Nexmark Benchmark 测试结果:整体性能提升 5.74 倍,极端重负载场景性能领先最高 18.3 倍。4. 100% 兼容 Apache Flink 1.20,业务代码零改造,无缝迁移。

华为云官方博客 384

LakeAgent实战 | 业务分析篇从一句提问到业务分析

本文以“订单金额变化”分析为例,展示LakeAgent如何通过自然语言提问,结合DataArtsStudio的数据资产与专业技能,帮助业务人员快速定位数据、生成SQL、追踪血缘、生成报告。从问题出发,逐步实现数据发现、变化洞察、原因追溯与团队协作,最终形成可复用的分析流程与企业规则,显著提升数据分析效率与一致性。

华为云官方博客 394

华为云RDS Supabase AI应用已开放公测 :30分钟搭建AI应用后端

认证、存储、实时推送、数据库API——4个后端模块,传统开发需要数周。今天,你只需要30分钟。AI创业公司的全栈开发工程师。上周老板说"下周要上线的AI对话应用,后端你来搞"——认证鉴权、数据库接口、文件存储、实时推送,每一个模块都需要数周搭建,而市场不会等你。小张打开华为云RDS Supabase,30分钟搭好认证、数据库API、对象存储,剩下的时间全用来打磨AI交互体验。基于开源Supabase+华为云RDS PostgreSQL全托管能力,为开发者提供。

华为云官方博客 402

如何使用RDS Supabase后端BaaS能力:数据库函数(Database Function)

上一篇介绍了 Database Data API——建表即自动生成增删改查接口,解决单表读写,但聚合统计、跨表事务、字段自动维护等逻辑已超出其能力边界。本篇推进到数据库函数(Database Function):把业务逻辑封装进数据库,前端通过 .rpc() 调用,由触发器自动响应数据变更。上一篇中,小张基于 Data API 完成了产品目录的建表与基础读写。但新需求随之而来:按分类统计产品数与价格区间、上架新品同时写产品表与定价表、产品修改后自动刷新更新时间。

华为云官方博客 467

如何使用RDS Supabase后端BaaS能力:创表即生成API(Database Data API)

认证、存储、实时推送、数据库API——4个后端模块,传统开发要数周。本系列逐个拆解,首篇聚焦数据库API(Database Data API):建表即得 API,无需手写后端。小张,AI创业公司的全栈工程师。老板让他做个产品目录页,展示产品与价格、支持上下架。传统做法要搭后端、写接口、配权限,工期少说1-2周;而在华为云RDS Supabase,建两张表、应用 SDK 直连,几分钟增删改查全跑通。

华为云官方博客 483

从一句话到一个能出去的链接,AI开发者空间做了什么

自行搭建的 Agent、正在使用的开源项目、团队既有的工具,都可以接入,可以通过AI创建或者手动搭建环境;华为云 AI开发者空间——面向开发者的云端一站式开发平台,做的就是把这段路接上:环境免安装,浏览器打开即为完整的 Linux 运行环境,预置的 AI 智能体开箱即用。第一个任务建议选能出结果的:做一个游戏、生成一个页面、创建一台实例,从需求走到可访问的链接,一次就能看清这个环境在做什么。沙箱是一个完整的 Linux 环境,依赖、服务、端口都是真实的,不是模拟。环境释放后,权限随之失效。

华为云官方博客 430

华为云码道配置自定义大模型,四种方式完整实操

案例介绍。

华为云官方博客 633

图数据库:实现穿透式监管的数据基座

穿透式监管标志着国企监管从“管企业”向“管资本”转型过程中的关键一跃。而图数据库正是这一跃的技术支点。从股权穿透到资金链穿透,从供应链穿透到数据穿透,华为GES图数据库正在将“穿透式监管”从理念变为可落地、可操作的实践。无论是天眼查式的全量链路查询,还是某集团财经的税务规则判断,抑或是芯片供应链式的部件风险分析,乃至通过Palantir构建决策知识图谱,其底层逻辑都是一致的——用图的力量,穿透一切形式的“隔离层”,直抵风险本质与责任核心。

华为云官方博客 668

《公共云三十问》正式收官,奔赴企业AI百场景新征程

今年6月底,公共云产业联盟重点推出《公共云三十问》专栏,以每周三问的节奏,连载十周,解读《先进公共云》白皮书,分享国计民生系列案例,旨在凝聚共识、推动落地实践。同时,专栏分析公共云在产业升级、民生福祉、科技创新、全球发展等方面的作用,梳理中国特色公共云发展路径:以普惠云拓展用云广度,以智算云提升用云深度,以自主云夯实用云厚度,以全球云打开用云跨度。这十问从专属云、行业云切入,深入政务、金融、交通、电力、钢铁、化工、油气、制造等国计民生场景,回答公共云“怎么用”。

华为云官方博客 474

开发者小技巧|IAM配置,让码道代码智能体连续30天免登录

案例介绍。

华为云官方博客 648

码道深度对接CodeArts,让需求自动落地为代码

案例介绍。

华为云官方博客 722

【公共云三十问 之三十】拥抱AI,企业如何建设面向智能体的专属云?

企业可分阶段建设覆盖开发、运行、运营及安全的一体化智能体平台——从构建一站式、高低码协同的智能体开发平台,到打造高性能、极致弹性的运行时环境,再到搭建AgentOps驱动的持续运营与运维体系,同时构建端到端、多层次的全链路安全防护体系。在数据湖建设方面,遵循“存量复用、平滑演进、AI导向、分步迭代”原则,分三阶段推进——打造AI多模数据湖核心能力、完成存算分离与统一元数据管理、实现闭环赋能全面支撑智能体规模化落地,在保护既有投资的前提下将传统数据平台转型为AI数据专属供给平台。

华为云官方博客 824

【公共云三十问 之二十九】国计民生之制造:制造云如何重塑数智时代的新质生产力?

平台打通软件交付的完整路径,覆盖需求管理、代码托管、代码检查、编译构建、制品仓库、部署、测试等关键环节,支持大模型能力融入研发全流程,实现从需求到交付的端到端管理。通过构建“研发一朵云”,方案打破地域与组织壁垒,实现跨团队协同开发,有效缩短软件交付周期,助力工业制造企业从“传统研发”走向“敏捷研发”,加速数智化转型进程。当制造云真正成为数据流通的平台、AI创新的底座,公共云的社会价值将从基础设施层向生产场景层持续延伸,为新型工业化建设和制造强国战略注入源源不断的动力,重塑数智时代的中国制造新质生产力。

华为云官方博客 791

告别重复手工迁仓,码道MCP+Skill实现仓库批量迁移

案例介绍。

华为云官方博客 791

【公共云三十问 之二十八】国计民生之油气:智算云如何叩响数智时代的“地心蓝金”?

西南油气田以“向地球深部要资源”为战略方向,联合以华为云Stack为代表的公共云服务商共同打造统一算力底座,建设西南区域数据湖与通用技术平台,构建“勘探开发工程技术、天然气生产过程管控、天然气产运储销”等三个一体化协同应用场景,支撑油气勘探、开发生产、管输储运等全业务链的数智化转型。当智算云真正成为数据流通的平台、AI创新的底座,油气田得以将算力效能、数据价值与智能技术深度融入勘探开发全链条,将“看不见的算力”转化为“摸得着的储量”,以智能化手段持续降低勘探风险、缩短发现周期、提高采收效率,确保生产安全。

华为云官方博客 613

【公共云三十问 之二十七】国计民生之化工:数智云如何重塑传统化工新范式?

在架构方面,方案构建云边协同的统一体系,涵盖AI协同、数据协同与运营协同三大维度——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现“边用边学”的智能闭环,模型平均精度提升20%以上。此外,方案提供一站式企业级工业物联网平台、数据湖与数据治理平台,覆盖工业数据分析“采、存、算、管、用”全流程,打通生产现场与管理系统间的数据壁垒,有效消除数据孤岛。层面,方案充分发挥软硬协同优势,实现计算、存储、网络与云平台的深度协同,训练线性度超95%,为大模型训练推理提供强劲算力支撑。▲化工“数智”新模式。

华为云官方博客 664

【公共云三十问 之二十六】国计民生之钢铁:数智云如何淬炼中国钢铁新力量?

同时,构建云边端协同的统一架构,支撑集团总部与各生产基地两级协同发展——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现训推一体化的资源调度与高效协同,为钢铁行业的数智化转型构建了坚实的算力底座。层面,方案构建覆盖“工业数采→边缘IT/OT融合→统一数据湖→数据治理平台→数据应用”全链路的一站式数据治理与流通平台,打通生产现场与管理系统间的数据壁垒,让数据“采得到、流得动、用得好”,加速数据价值从生产现场向经营决策层流动释放。湘潭钢铁集团的钢铁数智云,是钢铁工业智能云建设的标杆案例。

华为云官方博客 526

LakeWatch——MRS 智能运维Agent

从资源池、队列、作业等多个维度,对集群CPU与内存资源进行精细化分析与对比,直观呈现不同时间段的空闲、满载等使用状态,识别资源浪费,指导治理优化,为扩缩容决策提供数据支撑。对HDFS存储进行多维度深度分析(目录、用户、用户组等),涵盖使用量、增量、文件大小分布及数据热度等指标,帮助快速识别增长较快的目录、长期未访问的文件,统计目录文件数量与小文件占比,评估HDFS总存储量与扩缩容需求,为存储治理与资源规划提供全面数据支撑。Agent主动巡检,识别异常趋势并提前预警,让运维从"事后处理"转向"事前预防"。

华为云官方博客 515

华为云专题文档发布:丰富的AI案例、选型指导和概念解读

发布 “云服务选型”、“云计算概念”专题,提供云服务选型指导,助您选择匹配业务的云服务,解读云计算相关概念,开启云上学习之路。开发者工具和文档专区上线,提供从学习、实践到反馈的全链路支持。帮助中心新增100余篇AI案例。

华为云官方博客 286

一个人,也能用AI做点“星东西”

华为云星耀创想者大赛-2026华为云OPC创意大赛等你来~

华为云官方博客 291

从需求到可执行工作项:MCP+Skill 让智能体规划真正落地

回顾整个体验,以前让智能体帮我拆需求,它是洋洋洒洒给我列了一大篇 Markdown,看着挺像那么回事,但最后还是得我一条条往 CodeArts Req 里搬,搬完再手动挂父子关系、排日期,拆需求五分钟,搬运两小时。现在好了,MCP 给智能体接上了"手",Skill 给它装上了"项目经理的脑子",一句话下去,34 个工作项连带排期、层级关系全给我建好了,甘特图一拉,齐刷刷都在两周内,节省了多少工作量啊,美滋滋。本次实操在码道代码智能体基础上,构建了一套"需求→可执行工作项"的自动化规划方案。

华为云官方博客 576

华为云码道「码力续航计划」更新!

✅上新GLM‑5.3‑Flash,高速响应,代码生成、工程解析、Agent调试高效输出。新增GLM‑5.3‑Flash福利模型,每日解锁1000万免费Tokens。● 本次活动共发放总量价值100万元Tokens,发完即止!把耗算力的编码重活,交给华为云码道代码智能体🚀🚀。● 每日额度准时刷新,当日清零、次日满血重置。✅复杂算法、大项目解析、批量代码生成随便跑。✅不用抠额度、不用算消耗,放开调试大胆迭代。✅无需额外操作,登录即送,直接切换模型使用。● 把算力交给码道,让撸码不再内耗👏🏻。

华为云官方博客 688

【公共云三十问 之二十四】国计民生之交通:交通云如何铺就数智通衢新坦途?

交通行业作为支撑实体经济的关键基础设施,涵盖公路、铁路、机场、港口、地铁等众多子领域,其中,公路网络作为综合交通运输体系的基础骨架,承担着全国超过70%的客货运量,其数字化、智能化转型的需求尤为迫切。方面,提供Agent开发框架、知识库管理、RAG工程、意图识别、静态应用编排及模型评测等能力,支持客户将行业知识、业务流程与AI能力深度融合,快速构建面向收费稽核、路网调度、应急处置等场景的智能应用。开发并实现落地应用,全面覆盖建设、管理、养护、运营、服务、安全、办公七大业务领域。

华为云官方博客 464

【公共云三十问 之二十五】国计民生之电力:电力云如何点亮数智电力新引擎?

方案构建了覆盖数据获取、质量评估到标准化处理的全链路能力。基于公共云服务商智算底座及气象大模型,通过时空图神经网络重新建模产流机制,中国华电建成超50TB多源融合数据集,突破时空建模与机理融合等五大关键技术,短期径流预测精度较传统机理模型提升5个百分点以上,实现15天中期径流预测精度超85%,推理效率达秒级。基于气象大模型的基础架构与预训练能力,客户可直接在大模型之上开展增量训练与微调,充分利用场站所在区域的高精度历史数据(如3公里、1公里分辨率),针对不同场站和区域的地形地貌与气候特征进行模型调优。

华为云官方博客 381
上一篇: 仓颉之编译和构建的奇妙旅程
下一篇: 框架大冒险:开发者的奇幻之旅
华为云开发者联盟
华为云开发者联盟 企业官方账号 企业官方账号
博客等级 码龄10年 72万+粉丝 5985原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值