Julia 提供一系列控制流:
begin
和 (;)
if-elseif-else
和 ?: (ternary operator)
&&, ||
和 chained comparisons
while
和 for
try-catch
, error
和 throw
yieldto
前五個(gè)控制流機(jī)制是高級(jí)編程語言的標(biāo)準(zhǔn)。但任務(wù)不是:它提供了非本地的控制流,便于在臨時(shí)暫停的計(jì)算中進(jìn)行切換。在 Julia 中,異常處理和協(xié)同多任務(wù)都是使用的這個(gè)機(jī)制。
用一個(gè)表達(dá)式按照順序?qū)σ幌盗凶颖磉_(dá)式求值,并返回最后一個(gè)子表達(dá)式的值,有兩種方法:begin
塊和 (;)
鏈。 begin
塊的例子:
julia> z = begin
x = 1
y = 2
x + y
end
3
這個(gè)塊很短也很簡單,可以用 (;) 鏈語法將其放在一行上:
julia> z = (x = 1; y = 2; x + y)
3
這個(gè)語法在函數(shù)中的單行函數(shù)定義非常有用。 begin 塊也可以寫成單行, (;) 鏈也可以寫成多行:
julia> begin x = 1; y = 2; x + y end
3
julia> (x = 1;
y = 2;
x + y)
3
一個(gè) if
-elseif-else
條件表達(dá)式的例子:
if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
如果條件表達(dá)式 x < y
為真,相應(yīng)的語句塊將會(huì)被執(zhí)行;否則就執(zhí)行條件表達(dá)式 x > y
,如果結(jié)果為真,相應(yīng)的語句塊將被執(zhí)行;如果兩個(gè)表達(dá)式都是假,else
語句塊將被執(zhí)行。這是它用在實(shí)際中的例子:
julia> function test(x, y)
if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
end
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
elseif
及 else
塊是可選的。
請(qǐng)注意,非常短的條件語句(一行)在 Julia 中是會(huì)經(jīng)常使用短的電路評(píng)估(Short-Circuit Evaluation)實(shí)現(xiàn)的,具體細(xì)節(jié)在下一節(jié)中進(jìn)行概述。
如果條件表達(dá)式的值是除 true
和 false
之外的值,會(huì)出錯(cuò):
julia> if 1
println("true")
end
ERROR: type: non-boolean (Int64) used in boolean context
“問號(hào)表達(dá)式”語法 ?:
與 if-elseif-else
語法相關(guān),但是適用于單個(gè)表達(dá)式:
a ? b : c
?
之前的 a
是條件表達(dá)式,如果為 true
,就執(zhí)行 : 之前的 b
表達(dá)式,如果為 false
,就執(zhí)行 :
的 c
表達(dá)式。
用問號(hào)表達(dá)式來重寫,可以使前面的例子更加緊湊。先看一個(gè)二選一的例子:
julia> x = 1; y = 2;
julia> println(x < y ? "less than" : "not less than")
less than
julia> x = 1; y = 0;
julia> println(x < y ? "less than" : "not less than")
not less than
三選一的例子需要鏈?zhǔn)秸{(diào)用問號(hào)表達(dá)式:
julia> test(x, y) = println(x < y ? "x is less than y" :
x > y ? "x is greater than y" : "x is equal to y")
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
鏈?zhǔn)絾柼?hào)表達(dá)式的結(jié)合規(guī)則是從右到左。
與 if-elseif-else
類似,:
前后的表達(dá)式,只有在對(duì)應(yīng)條件表達(dá)式為 true
或 false
時(shí)才執(zhí)行:
julia> v(x) = (println(x); x)
v (generic function with 1 method)
julia> 1 < 2 ? v("yes") : v("no")
yes
"yes"
julia> 1 > 2 ? v("yes") : v("no")
no
"no"
&&
和 ||
布爾運(yùn)算符被稱為短路求值,它們連接一系列布爾表達(dá)式,僅計(jì)算最少的表達(dá)式來確定整個(gè)鏈的布爾值。這意味著: 在表達(dá)式 a && b
中,只有 a
為 true
時(shí)才計(jì)算子表達(dá)式 b
在表達(dá)式 a || b
中,只有 a
為 false
時(shí)才計(jì)算子表達(dá)式 b
&&
和 ||
都與右側(cè)結(jié)合,但 &&
比 ||
優(yōu)先級(jí)高:
julia> t(x) = (println(x); true)
t (generic function with 1 method)
julia> f(x) = (println(x); false)
f (generic function with 1 method)
julia> t(1) && t(2)
1
2
true
julia> t(1) && f(2)
1
2
false
julia> f(1) && t(2)
1
false
julia> f(1) && f(2)
1
false
julia> t(1) || t(2)
1
true
julia> t(1) || f(2)
1
true
julia> f(1) || t(2)
1
2
true
julia> f(1) || f(2)
1
2
false
這種方式在 Julia 里經(jīng)常作為 if
語句的一個(gè)簡潔的替代。 可以把 if <cond> <statement> end
寫成 <cond> && <statement> (讀作 <cond> *從而* <statement>)
。 類似地, 可以把 if ! <cond> <statement> end
寫成 <cond> || <statement>
(讀作 要不就 )。
例如, 遞歸階乘可以這樣寫:
julia> function factorial(n::Int)
n >= 0 || error("n must be non-negative")
n == 0 && return 1
n * factorial(n-1)
end
factorial (generic function with 1 method)
julia> factorial(5)
120
julia> factorial(0)
1
julia> factorial(-1)
ERROR: n must be non-negative
in factorial at none:2
非短路求值運(yùn)算符,可以使用數(shù)學(xué)運(yùn)算和基本函數(shù)中介紹的位布爾運(yùn)算符 &
和 |
:
julia> f(1) & t(2)
1
2
false
julia> t(1) | t(2)
1
2
true
&&
和 ||
的運(yùn)算對(duì)象也必須是布爾值( true
或 false
)。在任何地方使用一個(gè)非布爾值,除非最后一個(gè)進(jìn)入連鎖條件的是一個(gè)錯(cuò)誤:
julia> 1 && true
ERROR: type: non-boolean (Int64) used in boolean context
另一方面,任何類型的表達(dá)式可以使用在一個(gè)條件鏈的末端。根據(jù)前面的條件,它將被評(píng)估和返回:
julia> true && (x = rand(2,2))
2x2 Array{Float64,2}:
0.768448 0.673959
0.940515 0.395453
julia> false && (x = rand(2,2))
false
有兩種循環(huán)表達(dá)式: while
循環(huán)和 for
循環(huán)。下面是 while
的例子:
julia> i = 1;
julia> while i <= 5
println(i)
i += 1
end
1
2
3
4
5
上例也可以重寫為 for
循環(huán):
julia> for i = 1:5
println(i)
end
1
2
3
4
5
此處的 1:5
是一個(gè) Range
對(duì)象,表示的是 1, 2, 3, 4, 5 序列。 for
循環(huán)遍歷這些數(shù),將其逐一賦給變量 i
。 while
循環(huán)和 for
循環(huán)的另一區(qū)別是變量的作用域。如果在其它作用域中沒有引入變量 i
,那么它僅存在于 for
循環(huán)中。不難驗(yàn)證:
julia> for j = 1:5
println(j)
end
1
2
3
4
5
julia> j
ERROR: j not defined
有關(guān)變量作用域,詳見變量的作用域 。
通常,for
循環(huán)可以遍歷任意容器。這時(shí),應(yīng)使用另一個(gè)(但是完全等價(jià)的)關(guān)鍵詞 in
,而不是 =
,它使得代碼更易閱讀:
julia> for i in [1,4,0]
println(i)
end
1
4
0
julia> for s in ["foo","bar","baz"]
println(s)
end
foo
bar
baz
手冊中將介紹各種可迭代容器(詳見多維數(shù)組)。
有時(shí)要提前終止 while
或 for
循環(huán)??梢酝ㄟ^關(guān)鍵詞 break
來實(shí)現(xiàn):
julia> i = 1;
julia> while true
println(i)
if i >= 5
break
end
i += 1
end
1
2
3
4
5
julia> for i = 1:1000
println(i)
if i >= 5
break
end
end
1
2
3
4
5
有時(shí)需要中斷本次循環(huán),進(jìn)行下一次循環(huán),這時(shí)可以用關(guān)鍵字 continue
:
julia> for i = 1:10
if i % 3 != 0
continue
end
println(i)
end
3
6
9
多層 for
循環(huán)可以被重寫為一個(gè)外層循環(huán),迭代類似于笛卡爾乘積的形式:
julia> for i = 1:2, j = 3:4
println((i, j))
end
(1,3)
(1,4)
(2,3)
(2,4)
這種情況下用 break
可以直接跳出所有循環(huán)。
當(dāng)遇到意外條件時(shí),函數(shù)可能無法給調(diào)用者返回一個(gè)合理值。這時(shí),要么終止程序,打印診斷錯(cuò)誤信息;要么程序員編寫異常處理。
Exception
如果程序遇到意外條件,異常將會(huì)被拋出。表中列出內(nèi)置異常。
Exception |
---|
ArgumentError |
BoundsError |
DivideError |
DomainError |
EOFError |
ErrorException |
InexactError |
InterruptException |
KeyError |
LoadError |
MemoryError |
MethodError |
OverflowError |
ParseError |
SystemError |
TypeError |
UndefRefError |
UndefVarError |
例如,當(dāng)對(duì)負(fù)實(shí)數(shù)使用內(nèi)置的 sqrt
函數(shù)時(shí),將拋出 DomainError()
:
julia> sqrt(-1)
ERROR: DomainError
sqrt will only return a complex result if called with a complex argument.
try sqrt(complex(x))
in sqrt at math.jl:131
你可以使用下列方式定義你自己的異常:
julia> type MyCustomException <: Exception end
throw
函數(shù)可以使用 throw
函數(shù)顯式創(chuàng)建異常。例如,某個(gè)函數(shù)只對(duì)非負(fù)數(shù)做了定義,如果參數(shù)為負(fù)數(shù),可以拋出 DomaineError
異常:
julia> f(x) = x>=0 ? exp(-x) : throw(DomainError())
f (generic function with 1 method)
julia> f(1)
0.36787944117144233
julia> f(-1)
ERROR: DomainError
in f at none:1
注意,DomainError
使用時(shí)需要使用帶括號(hào)的形式,否則返回的并不是異常,而是異常的類型。必須帶括號(hào)才能返回 Exception
對(duì)象:
julia> typeof(DomainError()) <: Exception
true
julia> typeof(DomainError) <: Exception
false
另外,一些異常類型使用一個(gè)或更多個(gè)參數(shù)用來報(bào)告錯(cuò)誤:
julia> throw(UndefVarError(:x))
ERROR: x not defined
這個(gè)機(jī)制能被簡單實(shí)現(xiàn),通過按照下列所示的 UndefVarError
方法自定義異常類型:
julia> type MyUndefVarError <: Exception
var::Symbol
end
julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined");
error
函數(shù)error
函數(shù)用來產(chǎn)生 ErrorException
,阻斷程序的正常執(zhí)行。
如下改寫 sqrt
函數(shù),當(dāng)參數(shù)為負(fù)數(shù)時(shí),提示錯(cuò)誤,立即停止執(zhí)行:
julia> fussy_sqrt(x) = x >= 0 ? sqrt(x) : error("negative x not allowed")
fussy_sqrt (generic function with 1 method)
julia> fussy_sqrt(2)
1.4142135623730951
julia> fussy_sqrt(-1)
ERROR: negative x not allowed
in fussy_sqrt at none:1
當(dāng)對(duì)負(fù)數(shù)調(diào)用 fussy_sqrt
時(shí),它會(huì)立即返回,顯示錯(cuò)誤信息:
julia> function verbose_fussy_sqrt(x)
println("before fussy_sqrt")
r = fussy_sqrt(x)
println("after fussy_sqrt")
return r
end
verbose_fussy_sqrt (generic function with 1 method)
julia> verbose_fussy_sqrt(2)
before fussy_sqrt
after fussy_sqrt
1.4142135623730951
julia> verbose_fussy_sqrt(-1)
before fussy_sqrt
ERROR: negative x not allowed
in verbose_fussy_sqrt at none:3
warn
和 info
函數(shù)Julia 還提供一些函數(shù),用來向標(biāo)準(zhǔn)錯(cuò)誤 I/O 輸出一些消息,但不拋出異常,因而并不會(huì)打斷程序的執(zhí)行:
julia> info("Hi"); 1+1
INFO: Hi
2
julia> warn("Hi"); 1+1
WARNING: Hi
2
julia> error("Hi"); 1+1
ERROR: Hi
in error at error.jl:21
try/catch
語句try/catch
語句可以用于處理一部分預(yù)料中的異常 Exception
。例如,下面求平方根函數(shù)可以正確處理實(shí)數(shù)或者復(fù)數(shù):
julia> f(x) = try
sqrt(x)
catch
sqrt(complex(x, 0))
end
f (generic function with 1 method)
julia> f(1)
1.0
julia> f(-1)
0.0 + 1.0im
但是處理異常比正常采用分支來處理,會(huì)慢得多。
try/catch
語句使用時(shí)也可以把異常賦值給某個(gè)變量。例如:
julia> sqrt_second(x) = try
sqrt(x[2])
catch y
if isa(y, DomainError)
sqrt(complex(x[2], 0))
elseif isa(y, BoundsError)
sqrt(x)
end
end
sqrt_second (generic function with 1 method)
julia> sqrt_second([1 4])
2.0
julia> sqrt_second([1 -4])
0.0 + 2.0im
julia> sqrt_second(9)
3.0
julia> sqrt_second(-9)
ERROR: DomainError
in sqrt_second at none:7
注意,跟在 catch
之后的符號(hào)會(huì)被解釋為一個(gè)異常的名稱,因此,需要注意的是,在單行中寫 try/catch
表達(dá)式時(shí)。下面的代碼將不會(huì)正常工作返回 x
的值為了防止發(fā)生錯(cuò)誤:
try bad() catch x end
我們在 catch
后使用分號(hào)或插入換行來實(shí)現(xiàn):
try bad() catch; x end
try bad()
catch
x
end
Julia 還提供了更高級(jí)的異常處理函數(shù) rethrow
, backtrace
和 catch_backtrace
。
在改變狀態(tài)或者使用文件等資源時(shí),通常需要在操作執(zhí)行完成時(shí)做清理工作(比如關(guān)閉文件)。異常的存在使得這樣的任務(wù)變得復(fù)雜,因?yàn)楫惓?huì)導(dǎo)致程序提前退出。關(guān)鍵字 finally
可以解決這樣的問題,無論程序是怎樣退出的,finally
語句總是會(huì)被執(zhí)行。
例如, 下面的程序說明了怎樣保證打開的文件總是會(huì)被關(guān)閉:
f = open("file")
try
# operate on file f
finally
close(f)
end
當(dāng)程序執(zhí)行完 try
語句塊(例如因?yàn)閳?zhí)行到 return
語句,或者只是正常完成),close
語句將會(huì)被執(zhí)行。如果 try
語句塊因?yàn)楫惓L崆巴顺?,異常將?huì)繼續(xù)傳播。catch
語句可以和 try
,finally
一起使用。這時(shí)。finally
語句將會(huì)在 catch
處理完異常之后執(zhí)行。
任務(wù)是一種允許計(jì)算靈活地掛起和恢復(fù)的控制流,有時(shí)也被稱為對(duì)稱協(xié)程、輕量級(jí)線程、協(xié)同多任務(wù)等。
如果一個(gè)計(jì)算(比如運(yùn)行一個(gè)函數(shù))被設(shè)計(jì)為 Task
,有可能因?yàn)榍袚Q到其它 Task
而被中斷。原先的 Task
在以后恢復(fù)時(shí),會(huì)從原先中斷的地方繼續(xù)工作。切換任務(wù)不需要任何空間,同時(shí)可以有任意數(shù)量的任務(wù)切換,而不需要考慮堆棧問題。任務(wù)切換與函數(shù)調(diào)用不同,可以按照任何順序來進(jìn)行。
任務(wù)比較適合生產(chǎn)者-消費(fèi)者模式,一個(gè)過程用來生產(chǎn)值,另一個(gè)用來消費(fèi)值。消費(fèi)者不能簡單的調(diào)用生產(chǎn)者來得到值,因?yàn)閮烧叩膱?zhí)行時(shí)間不一定協(xié)同。在任務(wù)中,兩者則可以正常運(yùn)行。
Julia 提供了 produce
和 consume
函數(shù)來解決這個(gè)問題。生產(chǎn)者調(diào)用 produce
函數(shù)來生產(chǎn)值:
julia> function producer()
produce("start")
for n=1:4
produce(2n)
end
produce("stop")
end;
要消費(fèi)生產(chǎn)的值,先對(duì)生產(chǎn)者調(diào)用 Task
函數(shù),然后對(duì)返回的對(duì)象重復(fù)調(diào)用 consume
:
julia> p = Task(producer);
julia> consume(p)
"start"
julia> consume(p)
2
julia> consume(p)
4
julia> consume(p)
6
julia> consume(p)
8
julia> consume(p)
"stop"
可以在 for
循環(huán)中迭代任務(wù),生產(chǎn)的值被賦值給循環(huán)變量:
julia> for x in Task(producer)
println(x)
end
start
2
4
6
8
stop
注意 Task()
函數(shù)的參數(shù),應(yīng)為零參函數(shù)。生產(chǎn)者常常是參數(shù)化的,因此需要為其構(gòu)造零參匿名函數(shù) ??梢灾苯訉?,也可以調(diào)用宏:
function mytask(myarg)
...
end
taskHdl = Task(() -> mytask(7))
# 也可以寫成
taskHdl = @task mytask(7)
produce
和 consume
但它并不在不同的 CPU 發(fā)起線程。我們將在并行計(jì)算中,討論真正的內(nèi)核線程。
盡管 produce
和 consume
已經(jīng)闡釋了任務(wù)的本質(zhì),但是他們實(shí)際上是由庫函數(shù)調(diào)用更原始的函數(shù) yieldto
實(shí)現(xiàn)的。 yieldto(task,value)
掛起當(dāng)前任務(wù),切換到特定的 task
, 并使這個(gè) task
的最后一次 yeidlto
返回 \特定的 value
。注意 yieldto
是唯一需要的操作來進(jìn)行
‘任務(wù)風(fēng)格’的控制流;不需要調(diào)用和返回,我們只用在不同的任務(wù)之間切換即可。 這就是為什么這個(gè)特性被稱做 “對(duì)稱式協(xié)程”;每一個(gè)任務(wù)的切換都是用相同的機(jī)制。
yeildto
很強(qiáng)大, 但是大多數(shù)時(shí)候并不直接調(diào)用它。 當(dāng)你從當(dāng)前的任務(wù)切換走,你有可能會(huì)想切換回來, 但需要知道切換的時(shí)機(jī)和任務(wù),這會(huì)需要相當(dāng)?shù)膮f(xié)調(diào)。 例如,procude
需要保持某個(gè)狀態(tài)來記錄消費(fèi)者。無需手動(dòng)地記錄正在消費(fèi)的任務(wù)讓 produce
比 yieldto
更容易使用。
除此之外,為了高效地使用任務(wù),其他一些基本的函數(shù)也同樣必須。current_task()
獲得當(dāng)前運(yùn)行任務(wù)的引用。istaskdone(t)
查詢?nèi)蝿?wù)是否終止。istaskstarted(t) 查詢?nèi)蝿?wù)是否啟動(dòng)。task_local_storage
處理當(dāng)前任務(wù)的鍵值儲(chǔ)存。
大多數(shù)任務(wù)的切換都是在等待像 I/O 請(qǐng)求這樣的事件的時(shí)候,并由標(biāo)準(zhǔn)庫的調(diào)度器完成。調(diào)度器記錄正在運(yùn)行的任務(wù)的隊(duì)列,并執(zhí)行一個(gè)循環(huán)來根據(jù)外部事件(比如消息到達(dá))重啟任務(wù)。
處理等待事件的基本函數(shù)是 wait
。 有幾種對(duì)象實(shí)現(xiàn)了 wait
,比如對(duì)于 Process
對(duì)象, wait
會(huì)等待它終止。更多的時(shí)候 wait
是隱式的,比如 wait
可以發(fā)生在調(diào)用 read
的時(shí)候,等待數(shù)據(jù)變得可用。
在所有的情況中, wait
最終會(huì)操作在一個(gè)負(fù)責(zé)將任務(wù)排隊(duì)和重啟的 Condition
對(duì)象上。當(dāng)任務(wù)在 Condition
上調(diào)用 wait
, 任務(wù)會(huì)被標(biāo)記為不可運(yùn)行,被加入到 Condition
的 隊(duì)列中,再切換至調(diào)度器。調(diào)度器會(huì)選取另一個(gè)任務(wù)來運(yùn)行,或者等待外部事件。如果一切正常,最終一個(gè)事件句柄會(huì)在 Condition
上調(diào)用 notify
,使正在等待的任務(wù)變得可以運(yùn)行。
調(diào)用 Task
可以生成一個(gè)初始對(duì)調(diào)度器還未知的任務(wù),這允許你用 yieldto
手動(dòng)管理任務(wù)。不管怎樣,當(dāng)這樣的任務(wù)正在等待事件時(shí),事件一旦發(fā)生,它仍然會(huì)自動(dòng)重啟。而且任何時(shí)候你都可以調(diào)用 schedule(task)
或者用宏 @schedule
或 @async
來讓調(diào)度器來運(yùn)行一個(gè)任務(wù),根本不用去等待任何事件。(參見并行計(jì)算)
任務(wù)包含一個(gè) state
域,它用來描述任務(wù)的執(zhí)行狀態(tài)。任務(wù)狀態(tài)取如下的幾種符號(hào)中的一種:
符號(hào) | 意義 |
---|---|
:runnable | 任務(wù)正在運(yùn)行,或可被切換到該任務(wù) |
:waiting | 等待一個(gè)特定事件從而阻塞 |
:queued | 在調(diào)度程序的運(yùn)行隊(duì)列中準(zhǔn)備重新啟動(dòng) |
:done | 成功執(zhí)行完畢 |
:failed | 由于未處理的異常而終止 |
更多建議: