惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threat Research - Cisco Blogs
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tenable Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cisco Blogs
I
Intezer
Hacker News - Newest:
Hacker News - Newest: "LLM"
Hacker News: Ask HN
Hacker News: Ask HN
Schneier on Security
Schneier on Security
H
Heimdal Security Blog
Simon Willison's Weblog
Simon Willison's Weblog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Cyberwarzone
Cyberwarzone
V2EX - 技术
V2EX - 技术
W
WeLiveSecurity
Help Net Security
Help Net Security
S
Secure Thoughts
P
Privacy & Cybersecurity Law Blog
S
Securelist
SecWiki News
SecWiki News
P
Palo Alto Networks Blog
C
CERT Recently Published Vulnerability Notes
Know Your Adversary
Know Your Adversary
The Last Watchdog
The Last Watchdog
N
News | PayPal Newsroom
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
V
Vulnerabilities – Threatpost
H
Hacker News: Front Page
NISL@THU
NISL@THU
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Attack and Defense Labs
Attack and Defense Labs
Security Archives - TechRepublic
Security Archives - TechRepublic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Google Online Security Blog
Google Online Security Blog
The Hacker News
The Hacker News
Cloudbric
Cloudbric
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
A
Arctic Wolf
Latest news
Latest news
S
Schneier on Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
Project Zero
Project Zero
P
Privacy International News Feed
B
Blog
云风的 BLOG
云风的 BLOG

Jark's Blog

Flink 1.16:Hive SQL 如何平迁到 Flink SQL Flink CDC 如何简化实时数据入湖入仓 基于 Flink SQL 构建流批一体的 ETL 数据集成 Nexmark: 如何设计一个流计算基准测试? Demo:基于 Flink SQL 构建流式应用 Flink 1.9 实战:使用 SQL 读取 Kafka 并写入 MySQL Flink SQL 编程实践 如何从小白成长为 Apache Committer? 聊聊Blink开源和Flink社区近况 Flink 小贴士 (7): 4个步骤,让 Flink 应用达到生产状态 Flink 小贴士 (6): 使用 Broadcast State 的 4 个注意事项 Flink 小贴士 (5): Savepoint 和 Checkpoint 的 3 个不同点 Flink 小贴士 (4): 如何选择状态后端 Flink 小贴士 (3): 轻松理解 Watermark 一文了解 Apache Flink 核心技术 Flink 零基础实战教程:如何计算实时热门商品 5分钟从零构建第一个 Flink 应用 Flink 小贴士 (2):Flink 如何管理 Kafka 消费位点 Flink小贴士 (1):确定Flink作业所需资源大小时要考虑的6件事 Flink在美团的实践与应用 我在阿里的这两年 Flink 原理与实现:Aysnc I/O Flink 原理与实现:Table & SQL API Flink 原理与实现:Session Window Flink 原理与实现:Window 机制 Flink 原理与实现:数据流上的类型和操作 Flink 原理与实现:如何生成 JobGraph Flink 原理与实现:理解 Flink 中的计算资源 Flink 原理与实现:如何生成 StreamGraph Flink 原理与实现:架构和拓扑概览 Flink 原理与实现:内存管理 Flink 原理与实现:如何处理反压问题 Flink官方文档翻译:安装部署(集群模式) Flink官方文档翻译:安装部署(本地模式) 迟到的2015年终总结 高效Macbook开发之道(工具篇) Clojure学习笔记(二):语法 Clojure学习笔记(一):数据结构 Thrift 实践 Thrift 入门 Metrics 是个什么鬼 之入门教程 使用Redis和SQLAlchemy对Scrapy Item去重并存储 使用Scrapy定制可动态配置的爬虫 编程方式下运行 Scrapy spider 读《程序员必读的职业规划书》 Spark 下操作 HBase(1.0.0 新 API) HBase 集群安装部署 Spark On YARN 集群安装部署 Git 常用技能
Clojure学习笔记(三):并发与引用
WuChong · 2015-11-06 · via Jark's Blog

发表于   |   分类于 编程语言   |  

很多人是为了更好地进行并发编程而选择了Clojure,但Clojure所有的数据都是只读的,除非你使用引用类型(Vars、Ref、Atom、Agent)来标明它们是可以修改的。Clojure处理并发的思路与众不同,采用的是Software Transactional Memory (STM)来实现的,即软事务内存。你可以将STM想象成数据库,只不过是内存型的,它只支持事务的ACI,也就是原子性、一致性、隔离性,但是不包括持久性,因为状态的保存都在内存里。引用类型是一种可变引用指向不可变数据的一种机制。Clojure的并发API分为四种模型:

  1. 管理Thread local变量的Var。
  2. 管理协作式、同步修改可变状态的Ref
  3. 管理非协作式、同步修改可变状态的Atom
  4. 管理异步修改可变状态的Agent

Vars

Vars 是一种引用类型,它可以有一个被所有线程共享的root binding,并且每个线程还能拥有自己(thread-local)的值。

  • def
    def 定义, 将会影响全局定义。

    (def v 2) ; -> 2
  • let
    let 定义, 将会影响自身生命周期内,以及自己作用域内,如果超出自己的作用域,则无效。

    (def name "jark")
    (let [name "wuchong"]
    (println name)) ; -> 输出 wuchong
    (println name) ; -> 输出 jark
  • binding
    binding, 将会影响自身生命周期以及自己作用域内,即使超出自身作用域,也都有效。

    这个例子演示了binding和set!一起使用,用set!来修改一个由binding bind的Var的线程本地的值。

    (def ^:dynamic v 1) ; 需要声明成"dynamic",v才能用binding来改变值。

    (defn change-it []
    (println "2) v =" v) ; -> 1

    (def v 2) ; changes root value
    (println "3) v =" v) ; -> 2

    (binding [v 3] ; binds a thread-local value
    (println "4) v =" v) ; -> 3

    (set! v 4) ; changes thread-local value
    (println "5) v =" v)) ; -> 4

    (println "6) v =" v)) ; thread-local value is gone now -> 2

    (println "1) v =" v) ; -> 1

    (let [thread (Thread. #(change-it))] ; 启动一个本地线程
    (.start thread)
    (.join thread)) ; 等待线程结束

    (println "7) v =" v) ; -> 2

好吧,说了这么多,其实Clojure不鼓励我们使用Vars,因为Vars在线程间不能很好地协作。

Refs

Refs是用来协调对于一个或者多个binding的并发修改的。

  • ref

    ; 用ref函数创建一个可变的引用(reference),一个空的歌曲集合
    (def song (ref #{}))
    (println @song) ; -> #{} 用@来读取ref值
  • validator
    类似数据库,可以为ref添加“约束”,在数据更新的时候需要通过validator函数的验证,如果验证不通过,整个事务将回滚。

    (def validate-song
    (partial every? #(not (nil? %)))) ; 定义了歌名不能为空的validator
    (def song (ref #{} :validator validate-song))
  • dosync & ref-set

    ; 改变引用指向的内容,使用ref-set函数
    (ref-set song #{"Dangerous"})
    ; -> IllegalStateException: No transaction running
    ; 会报错,因为引用是可变的,对状态的更新需要用事务进行保护,使用dosync
    (dosync (ref-set song #{"Dangerous"}))

    ; 因为我们加了不能为空的validator,加入空会报错
    (dosync (ref-set song #{})) ; -> IllegalStateException Invalid reference state

    ; 可以对多个ref的更新放在一个事务里
    (dosync (ref-set song #{"Dangerous"})
    (ref-set singer #{"MJ"}) )
  • alter & commute
    更改引用有点暴力也比较少见,更常见的更新是根据当前状态更新,比如加一首歌进去。

    ; 先查询集合内容,然后往集合里添加歌曲,然后更新整个集合
    (dosync (ref-set song (conj @song "heal the world")))

    ; 查询并更新的操作可以合成一步,这是通过alter函数实现
    (dosync (alter song conj "heal the world"))

    (println @song) ; -> #{heal the world}

注意alter后跟的函数会把ref值当做第一个参数,所以这里使用cons就不行了,因为cons要求第一个参数是加入的元素。

commute函数是是对alter的优化,commute可以同时进行修改(并不影响ref最终的值)。通常情况下,一般优先使用alter,除非在遇到明显的性能瓶颈并且对顺序不是那么关心的时候,可以考虑用commute替换。

Atoms

Atoms 提供了比使用Refs&STM更简单的更新当个值的方法。它不受事务的影响。有点像Java的原子类(Atomic)。

有三个函数可以修改一个Atom的值:reset!,compare-and-set!swap!

(def counter (atom 1))  ; 指定 counter 为Atom类型
(reset! counter 2) ; 更新原子的值
(println @counter) ; -> 2 deref,用`@`读取atom的值
(compare-and-set! counter 2 3) ; -> true 执行成功
(swap! counter inc) ; 第二个参数是计算Atom新值的函数,可带参数。会一直执行直到成功为止。

Agents

Agents 是用来把一些事情放到另外一个线程来做(一般不需要事务控制的),用来控制状态的异步更新。

(def counter (agent 0)) ; 使用agent函数定义一个初始值为0的agent
(println @counter) ; -> 0 同样的使用@读取值

; 更新agent,通过send函数给agent发送任务去更新agent
(send counter inc) ; -> #<Agent@9444d1: 0> 此处一般是0,因为更新是异步的
(println @counter) ; -> 1 这里获取的肯定是1了,已经更新了

; 还有个方法,send-off,它的作用于send类似:
(send-off counter inc)
(println @counter) ; -> 2

send和send-off的区别在于,send是将任务交给一个固定大小的线程池执行(默认大小是CPU核数+2)。因此send执行的任务最好不要有阻塞的操作。而send-off则使用没有大小限制(取决于内存)的线程池。因此,send-off比较适合任务有阻塞的操作,如IO读写之类。注意,所有的agent是共用这些线程池。

扩展阅读

这篇笔记原先是想放在一篇文章里的,谁知太长了,只好分成了三篇。以下是我觉得学习Clojure不错的网上资源,需者自取。


hoxis wechat