Genel Bakış
Skyframe StateMachine
, başka bir yerde bulunan deyapılandırılmış bir işlev nesnesi
yığmaktır. Gerektiğinde esneklik ve değerlendirmeyi hem de1
gerekli değerler hemen kullanıma sunulmaz ancak eşzamansız olarak hesaplanır. İlgili içeriği oluşturmak için kullanılan
StateMachine
, beklemedeyken bir iş parçacığı kaynağını bağlayamaz, ancak bunun yerine şunları yapması gerekir:
askıya alınabilir ve devam ettirilebilir. Bu şekilde imha işlemi net bir şekilde yeniden girişi ortaya çıkarır
Böylece önceki hesaplamaların atlanabilmesini sağlayabilirsiniz.
StateMachine
öğeleri; dizileri, dalları, yapılandırılmış mantıksal ifadeyi ifade etmek için kullanılabilir.
ve Skyframe etkileşimi için özel olarak tasarlanır.
StateMachine
öğeleri, daha büyük StateMachine
öğeleri halinde oluşturulabilir ve paylaşılabilir.
alt StateMachine
Eşzamanlılık yapısı gereği her zaman hiyerarşiktir
tamamen mantıksaldır. Eşzamanlı her alt görev, paylaşılan tek bir üst öğede çalışır
SkyFunction iş parçacığı.
Giriş
Bu bölüm, şu konumda bulunan StateMachine
'ları kısaca motive eder ve tanıtır:
java.com.google.devtools.build.skyframe.state
paketinden yararlanın.
Skyframe'in yeniden başlatılmasına kısa bir giriş
Skyframe, bağımlılık grafiklerinin paralel olarak değerlendirildiği bir çerçevedir.
Grafikteki her düğüm bir SkyFunction'ın değerlendirmesine karşılık gelir ve
SkyKey, parametrelerini ve SkyValue'yu sonucunu belirtiyor. İlgili içeriği oluşturmak için kullanılan
SkyFunction'ın SkyKey ile SkyValues'u arayabilmesi,
ek SkyFunctions'ın yinelemeli ve paralel değerlendirmesini tetikliyor. Şunun yerine:
SkyValue'nun henüz gerçekleştirilmediği bir işlem olduğunda, bir iş parçacığının bağlanmasına
hesaplamanın bazı alt grafikleri tamamlanmadığından hazır,
SkyFunction bir null
getValue
yanıtı gözlemliyor ve null
değerini döndürecek
SkyValue yerine, eksik girişler nedeniyle eksik olduğunu belirten bir işaret oluşturabilir.
Skyframe, daha önce istenen tüm SkyValues değerleri olduğunda SkyFunctions'ı yeniden başlatır
kullanılabilir hale gelir.
SkyKeyComputeState
kullanıma sunulmadan önce,
hesaplamanın tamamen yeniden çalıştırılmasıydı. Bu, ikinci dereceden
ve bu şekilde yazılan işlevler en sonunda tamamlanır çünkü her tekrar,
null
döndüren arama sayısı daha az. SkyKeyComputeState
ile şunları yapabilirsiniz:
kontrol noktası verilerini SkyFunction ile ilişkilendirerek
yeniden hesaplamadır.
StateMachine
, SkyKeyComputeState
içinde yaşayan ve
SkyFunction yeniden başlatıldığında hemen hemen tüm yeniden hesaplamaları (
SkyKeyComputeState
, askıya alma ve devam ettirme özelliğini uygulayarak önbellekten çıkmaz)
yürütme kancalarıdır.
SkyKeyComputeState
içindeki durum bilgili hesaplamalar
Nesne odaklı tasarım açısından bakıldığında, depolama alanı
SkyKeyComputeState
içindeki işlemsel nesneleri içerir.
Java'da, nesne taşıyan bir davranışın asgari tanımı bir
işlevsel arayüze sahip olduğunu varsayalım. Bir StateMachine
şu tanım2:
@FunctionalInterface
public interface StateMachine {
StateMachine step(Tasks tasks) throws InterruptedException;
}
Tasks
arayüzü, SkyFunction.Environment
arayüzüne benzer ancak
eşzamansızlık için tasarlanmıştır ve mantıksal olarak eşzamanlı alt görevler için destek ekler3.
step
işlevinin döndürülen değeri başka bir StateMachine
ve spesifikasyona izin veriyor.
adımlardan oluşur. step
,DONE
StateMachine
tamamlandı. Örneğin:
class HelloWorld implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
System.out.println("hello");
return this::step2; // The next step is HelloWorld.step2.
}
private StateMachine step2(Tasks tasks) {
System.out.println("world");
// DONE is special value defined in the `StateMachine` interface signaling
// that the computation is done.
return DONE;
}
}
aşağıdaki çıkışa sahip bir StateMachine
öğesini açıklar.
hello
world
this::step2
yöntem referansının da aynı zamanda bir StateMachine
step2
StateMachine
ürününün işlevsel arayüz tanımına uygun. Yöntem
referanslar, bir sonraki durumu belirtmenin en yaygın yoludur
StateMachine
Sezgisel olarak, bir hesaplamayı StateMachine
bir örneği askıya almak ve devam ettirmek için gereken kancaları
hesaplamadır. StateMachine.step
geri döndüğünde açık bir askıya alma işlemi uygulanır
puan. Döndürülen StateMachine
değeri tarafından belirtilen devamlılık
açık resume noktası. Böylece, yeniden hesaplama yapmaktan kaçınabilirsiniz çünkü
işlemeye kaldığımız yerden devam edebilirsiniz.
Geri çağırmalar, devamlılıklar ve eşzamansız hesaplama
Teknik açıdan bakıldığında StateMachine
, bir devamlılık görevi görür ve
yürütülecek sonraki hesaplamadır. StateMachine
, engellemek yerine şunları yapabilir:
kendi isteğiyle askıya alma; step
kontrolü tekrar Driver
örneğine döner. Driver
şunları yapabilir:
ardından hazır bir StateMachine
moduna geçin veya kontrolü tekrar Skyframe'e bırakın.
Geleneksel olarak geri çağırmalar ve devamlılık işlemleri tek bir kavramda kullanılır.
Ancak StateMachine
öğeleri arasında bir ayrım vardır.
- Geri çağırma: Eşzamansız sonucun nerede depolanacağını açıklar hesaplamadır.
- Devam: Bir sonraki yürütme durumunu belirtir.
Eşzamansız bir işlem çağrılırken geri çağırmalar gerekir. Diğer bir deyişle, gerçek işlem, yöntem çağrıldıktan hemen sonra gerçekleşmez (örneğin, olduğunu doğrulayabilirsiniz. Geri çağırma işlevleri mümkün olduğunca basit olmalıdır.
Devamlar, StateMachine
ve StateMachine
değerlerinin döndürdüğü değerlerdir.
tümü eşzamansız bir şekilde izleyen karmaşık yürütme sürecini özetler
çözülür. Bu yapılandırılmış yaklaşım
planınızın karmaşıklığını
geri arama özelliğini kullanabilirsiniz.
Görevler
Tasks
arayüzü, StateMachine
kullanıcılarına SkyValues'u aramak için bir API sağlar
ve eşzamanlı alt görevler
planlamak için kullanılabilmesidir.
interface Tasks {
void enqueue(StateMachine subtask);
void lookUp(SkyKey key, Consumer<SkyValue> sink);
<E extends Exception>
void lookUp(SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink);
// lookUp overloads for 2 and 3 exception types exist, but are elided here.
}
SkyValue aramaları
StateMachine
, SkyValues'u aramak için Tasks.lookUp
aşırı yükleme kullanıyor. Bunlar:
SkyFunction.Environment.getValue
ve benzeri
SkyFunction.Environment.getValueOrThrow
ve benzer istisna işlemeye sahip
anlambilim. Uygulama, aramayı hemen gerçekleştirmez
bunun yerine, mümkün olduğunca fazla aramayı4 toplu olarak işler. Değer
hemen kullanılamayabilir (örneğin, Skyframe yeniden başlatılması gerektiğinde,
Bu nedenle, çağrı, bir geri çağırma kullanarak sonuçta ortaya çıkacak değerle ne yapılacağını belirtir.
StateMachine
işlemci (Driver
ve köprü
SkyFrame), değerin önceden görüntülendikten sonra mevcut olacağını garanti eder
durumu başlar. Bir örnek aşağıda verilmiştir.
class DoesLookup implements StateMachine, Consumer<SkyValue> {
private Value value;
@Override
public StateMachine step(Tasks tasks) {
tasks.lookUp(new Key(), (Consumer<SkyValue>) this);
return this::processValue;
}
// The `lookUp` call in `step` causes this to be called before `processValue`.
@Override // Implementation of Consumer<SkyValue>.
public void accept(SkyValue value) {
this.value = (Value)value;
}
private StateMachine processValue(Tasks tasks) {
System.out.println(value); // Prints the string representation of `value`.
return DONE;
}
}
Yukarıdaki örnekte, ilk adım new Key()
için bir arama yapar.
tüketici olarak this
. Bu mümkündür çünkü DoesLookup
,
Consumer<SkyValue>
.
Sözleşme uyarınca, bir sonraki DoesLookup.processValue
eyalet başlamadan önce
DoesLookup.step
aramaları tamamlandı. Dolayısıyla value
, şu durumlarda kullanılabilir:
processValue
üzerinden erişilir.
Alt görevler
Tasks.enqueue
, mantıksal olarak eşzamanlı alt görevlerin yürütülmesini ister.
Alt görevler de StateMachine
görevleri ve normal StateMachine
görevleriyle her şeyi yapabilir.
daha fazla alt görev oluşturma veya SkyValue'ları arama gibi özellikleri kullanabilirsiniz.
lookUp
gibi, durum makine sürücüsü de tüm alt görevlerin
tamamlamanız gerekir. Bir örnek aşağıda verilmiştir.
class Subtasks implements StateMachine {
private int i = 0;
@Override
public StateMachine step(Tasks tasks) {
tasks.enqueue(new Subtask1());
tasks.enqueue(new Subtask2());
// The next step is Subtasks.processResults. It won't be called until both
// Subtask1 and Subtask 2 are complete.
return this::processResults;
}
private StateMachine processResults(Tasks tasks) {
System.out.println(i); // Prints "3".
return DONE; // Subtasks is done.
}
private class Subtask1 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
i += 1;
return DONE; // Subtask1 is done.
}
}
private class Subtask2 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
i += 2;
return DONE; // Subtask2 is done.
}
}
}
Subtask1
ve Subtask2
mantıksal olarak eşzamanlı olsa da her şey
tek iş parçacığı, dolayısıyla "eşzamanlı" i
güncellemesinin hiçbirine gerek yok
senkronize edilir.
Yapılandırılmış eşzamanlılık
Her lookUp
ve enqueue
bir sonrakine geçmeden önce çözülmesi gerektiğinden
eş zamanlılığın doğal olarak ağaç yapılarıyla sınırlı olduğu anlamına gelir. İnsanların
aşağıda gösterildiği gibi hiyerarşik5 eşzamanlılık oluşturmak mümkündür
örneğine bakalım.
Eşzamanlılık yapısının bir ağaç oluşturduğunu UML'den anlamak zordur. Mevcut alternatif bir görünüm ağaç yapısına bakalım.
Yapılandırılmış eşzamanlılık hakkında akıl yürütebilmek çok daha kolaydır.
Düzen ve kontrol akışı kalıpları
Bu bölümde, birden fazla StateMachine
öğesinin nasıl oluşturulabileceğine dair örnekler sunulmaktadır
ve belirli kontrol akışı sorunlarına çözümler.
Sıralı durumlar
Bu, en yaygın ve basit kontrol akışı kalıbıdır. Örnek:
Bu, içindeki durum bilgili hesaplamalar bölümünde
SkyKeyComputeState
değerleridir.
Dallara ayırma
StateMachine
eyaletlerindeki dallar, farklı kullanıcılar döndürerek sağlanabilir.
değerlerini normal Java kontrol akışı kullanarak oluşturun.
class Branch implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
// Returns different state machines, depending on condition.
if (shouldUseA()) {
return this::performA;
}
return this::performB;
}
…
}
Belirli şubelerin erken tamamlanmak için DONE
iade etmesi çok yaygın bir durumdur.
Gelişmiş sıralı bileşim
StateMachine
kontrol yapısı belleksiz olduğundan StateMachine
paylaşılıyor.
alt görev tanımları bazen garip olabiliyor. M1 ve
M2; StateMachine
, S ve diğer benzer ölçekleri paylaşan StateMachine
M1 ve M2, <A, S, B> ve
Sırasıyla <X, S, Y>. Sorun, S'nin aşağıdakileri yapıp yapmayacağını bilmemesidir:
tamamlandıktan sonra B veya Y'ye devam eder ve StateMachine
'lar
çağrı yığını. Bu bölümde, bunu başarmaya yönelik bazı teknikler incelenmektedir.
Terminal sıra öğesi olarak StateMachine
Bu ise ilk ortaya çıkan sorunu çözmez. Yalnızca sıralı
paylaşılan StateMachine
dizide terminal olduğunda kompozisyon oluşturun.
// S is the shared state machine.
class S implements StateMachine { … }
class M1 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performA();
return new S();
}
}
class M2 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performX();
return new S();
}
}
S karmaşık bir durum makinesi olsa bile bu yöntem işe yarar.
Sıralı beste için alt görev
Sıraya alınan alt görevlerin bir sonraki durumdan önce tamamlanması garanti edildiğinden, alt görev mekanizmasının hafif kötüye kullanımı6 mümkündür.
class M1 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performA();
// S starts after `step` returns and by contract must complete before `doB`
// begins. It is effectively sequential, inducing the sequence < A, S, B >.
tasks.enqueue(new S());
return this::doB;
}
private StateMachine doB(Tasks tasks) {
performB();
return DONE;
}
}
class M2 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performX();
// Similarly, this induces the sequence < X, S, Y>.
tasks.enqueue(new S());
return this::doY;
}
private StateMachine doY(Tasks tasks) {
performY();
return DONE;
}
}
runAfter
yerleştirme
Bazı durumlarda, başka koşullar nedeniyle Tasks.enqueue
kötüye kullanımı
S öncesinde tamamlanması gereken paralel alt görevler veya Tasks.lookUp
çağrı
yürütülür. Bu durumda S içine runAfter
parametresi ekleme işlemi şunun için kullanılabilir:
S'yi bundan sonra ne yapacağı konusunda bilgilendirir.
class S implements StateMachine {
// Specifies what to run after S completes.
private final StateMachine runAfter;
@Override
public StateMachine step(Tasks tasks) {
… // Performs some computations.
return this::processResults;
}
@Nullable
private StateMachine processResults(Tasks tasks) {
… // Does some additional processing.
// Executes the state machine defined by `runAfter` after S completes.
return runAfter;
}
}
class M1 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performA();
// Passes `this::doB` as the `runAfter` parameter of S, resulting in the
// sequence < A, S, B >.
return new S(/* runAfter= */ this::doB);
}
private StateMachine doB(Tasks tasks) {
performB();
return DONE;
}
}
class M2 implements StateMachine {
@Override
public StateMachine step(Tasks tasks) {
performX();
// Passes `this::doY` as the `runAfter` parameter of S, resulting in the
// sequence < X, S, Y >.
return new S(/* runAfter= */ this::doY);
}
private StateMachine doY(Tasks tasks) {
performY();
return DONE;
}
}
Bu yaklaşım, alt görevleri kötüye kullanmaktan daha nettir. Ancak, bunu da uygulamak
serbest bir şekilde, örneğin birden fazla StateMachine
öğesini runAfter
ile iç içe yerleştirerek
Callback Hell'e giden yolda. Daha iyi sonuç verir.
yerine normal sıralı durumlara sahip runAfter
öğeleri.
return new S(/* runAfter= */ new T(/* runAfter= */ this::nextStep))
şunlarla değiştirilebilir.
private StateMachine step1(Tasks tasks) {
doStep1();
return new S(/* runAfter= */ this::intermediateStep);
}
private StateMachine intermediateStep(Tasks tasks) {
return new T(/* runAfter= */ this::nextStep);
}
Yasak alternatifi: runAfterUnlessError
Önceki bir taslakta, iptal edecek bir runAfterUnlessError
kullanmayı düşünüyorduk
erken hatalardan kurtulun. Hataların genellikle hatalarla uğraşarak
Biri runAfter
referansı olan StateMachine
tarafından olmak üzere iki kez kontrol edildi ve
bir kez runAfter
makinesinin kendisi tarafından yapıldı.
Biraz düşündükten sonra, kodun tek tipliğinin daha fazla olduğuna karar verdik.
daha önemlidir. Yalnızca URL'nin
runAfter
mekanizması
Her zaman hata kontrolü gerektiren tasks.enqueue
mekanizması.
Doğrudan yetki
Resmi durum geçişi her yapıldığında ana Driver
döngüsü ilerler.
Sözleşmeye göre, ilerleme durumu, daha önce sıraya alınan tüm SkyValue'nun
önceki durum yürütülmeden önce aramalar ve alt görevler çözülür. Bazen mantığın
StateMachine
yetki verilmişse aşama aşama ilerlemesini gereksiz kılar veya
olumsuz etki yaratabilir. Örneğin, yetki verilen kullanıcının ilk step
'si işlem gerçekleştirirse
Yetki veren duruma ilişkin aramalarla paralel yapılabilecek SkyKey aramaları
bir aşama avansı
bunları sıralı hale getirir. Bu kurstan sonra
aşağıdaki örnekte gösterildiği gibi doğrudan yetki verilmelidir.
class Parent implements StateMachine {
@Override
public StateMachine step(Tasks tasks ) {
tasks.lookUp(new Key1(), this);
// Directly delegates to `Delegate`.
//
// The (valid) alternative:
// return new Delegate(this::afterDelegation);
// would cause `Delegate.step` to execute after `step` completes which would
// cause lookups of `Key1` and `Key2` to be sequential instead of parallel.
return new Delegate(this::afterDelegation).step(tasks);
}
private StateMachine afterDelegation(Tasks tasks) {
…
}
}
class Delegate implements StateMachine {
private final StateMachine runAfter;
Delegate(StateMachine runAfter) {
this.runAfter = runAfter;
}
@Override
public StateMachine step(Tasks tasks) {
tasks.lookUp(new Key2(), this);
return …;
}
// Rest of implementation.
…
private StateMachine complete(Tasks tasks) {
…
return runAfter;
}
}
Veri akışı
Bir önceki tartışmada kontrol akışının yönetilmesine odaklandık. Bu bölümünde, veri değerlerinin yayılımı açıklanmaktadır.
Tasks.lookUp
geri çağırma uygulama
SkyValue'da bir Tasks.lookUp
geri çağırması uygulamaya ilişkin bir örnek bulunmaktadır
arama. Bu bölümde gerekçe sunulur ve
yaklaşımlarını öğreneceksiniz.
Tasks.lookUp
geri arama
Tasks.lookUp
yöntemi, parametre olarak bir geri çağırma (sink
) alır.
void lookUp(SkyKey key, Consumer<SkyValue> sink);
Deyimsel yaklaşım, şunu uygulamak için bir Java lambdası kullanmak olur:
tasks.lookUp(key, value -> myValue = (MyValueClass)value);
myValue
, şunu yapan StateMachine
örneğinin üye değişkenidir:
araması yapın. Ancak lambda, Google'ın açık kaynak koduna kıyasla
Consumer<SkyValue>
arayüzünü StateMachine
uygulamasında uygulama
hakkında bilgi edindiniz. Lambda, bir dizi arama terimini içeren birden fazla arama
muğlak olur.
Ayrıca, Tasks.lookUp
SkyFunction.Environment.getValueOrThrow
.
<E extends Exception> void lookUp(
SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink);
interface ValueOrExceptionSink<E extends Exception> {
void acceptValueOrException(@Nullable SkyValue value, @Nullable E exception);
}
Aşağıda örnek bir uygulama gösterilmiştir.
class PerformLookupWithError extends StateMachine, ValueOrExceptionSink<MyException> {
private MyValue value;
private MyException error;
@Override
public StateMachine step(Tasks tasks) {
tasks.lookUp(new MyKey(), MyException.class, ValueOrExceptionSink<MyException>) this);
return this::processResult;
}
@Override
public acceptValueOrException(@Nullable SkyValue value, @Nullable MyException exception) {
if (value != null) {
this.value = (MyValue)value;
return;
}
if (exception != null) {
this.error = exception;
return;
}
throw new IllegalArgumentException("Both parameters were unexpectedly null.");
}
private StateMachine processResult(Tasks tasks) {
if (exception != null) {
// Handles the error.
…
return DONE;
}
// Processes `value`, which is non-null.
…
}
}
Hata işleme gerektirmeyen aramalarda olduğu gibi doğrudan StateMachine
sınıfını kullanmak
uygulanması, lamba için bir bellek tahsisi kaydeder.
Hata işleme biraz daha ayrıntılı bilgi sağlar, ancak aslında hataların yayılması ile normal değerler arasında çok fazla fark yoktur.
Birden çok SkyValues kullanma
Genellikle birden çok SkyValue araması yapılması gerekir. Çevik yaklaşımın SkyValue türünü açacağım. Aşağıdaki örnekteki gibi , prototip üretim kodundan basitleştirilmiştir.
@Nullable
private StateMachine fetchConfigurationAndPackage(Tasks tasks) {
var configurationKey = configuredTarget.getConfigurationKey();
if (configurationKey != null) {
tasks.lookUp(configurationKey, (Consumer<SkyValue>) this);
}
var packageId = configuredTarget.getLabel().getPackageIdentifier();
tasks.lookUp(PackageValue.key(packageId), (Consumer<SkyValue>) this);
return this::constructResult;
}
@Override // Implementation of `Consumer<SkyValue>`.
public void accept(SkyValue value) {
if (value instanceof BuildConfigurationValue) {
this.configurationValue = (BuildConfigurationValue) value;
return;
}
if (value instanceof PackageValue) {
this.pkg = ((PackageValue) value).getPackage();
return;
}
throw new IllegalArgumentException("unexpected value: " + value);
}
Consumer<SkyValue>
geri çağırma uygulaması açık bir şekilde paylaşılabilir
çünkü değer türleri farklı. Böyle bir durum söz konusu değilse
lambda tabanlı uygulamalar veya
uygun geri çağırmalar yapılmalıdır.
Değerleri StateMachine
saniyeler arasında yayma
Şu ana kadar bu belgede yalnızca bir alt görevdeki işlerin nasıl düzenleneceği anlatılmıştı ancak alt görevlerin çağrı yapana bir değer bildirmesi de gerekir. Alt görevler mantıksal olarak eşzamansız olduğunda, sonuçları geri arama. Bunun işe yaraması için alt görev, enjekte edilir.
class BarProducer implements StateMachine {
// Callers of BarProducer implement the following interface to accept its
// results. Exactly one of the two methods will be called by the time
// BarProducer completes.
interface ResultSink {
void acceptBarValue(Bar value);
void acceptBarError(BarException exception);
}
private final ResultSink sink;
BarProducer(ResultSink sink) {
this.sink = sink;
}
… // StateMachine steps that end with this::complete.
private StateMachine complete(Tasks tasks) {
if (hasError()) {
sink.acceptBarError(getError());
return DONE;
}
sink.acceptBarValue(getValue());
return DONE;
}
}
Bu durumda arayan (StateMachine
) aşağıdaki gibi görünür.
class Caller implements StateMachine, BarProducer.ResultSink {
interface ResultSink {
void acceptCallerValue(Bar value);
void acceptCallerError(BarException error);
}
private final ResultSink sink;
private Bar value;
Caller(ResultSink sink) {
this.sink = sink;
}
@Override
@Nullable
public StateMachine step(Tasks tasks) {
tasks.enqueue(new BarProducer((BarProducer.ResultSink) this));
return this::processResult;
}
@Override
public void acceptBarValue(Bar value) {
this.value = value;
}
@Override
public void acceptBarError(BarException error) {
sink.acceptCallerError(error);
}
private StateMachine processResult(Tasks tasks) {
// Since all enqueued subtasks resolve before `processResult` starts, one of
// the `BarResultSink` callbacks must have been called by this point.
if (value == null) {
return DONE; // There was a previously reported error.
}
var finalResult = computeResult(value);
sink.acceptCallerValue(finalResult);
return DONE;
}
}
Yukarıdaki örnekte birkaç şey açıklanmaktadır. Caller
,
kendi Caller.ResultSink
değerini tanımlar. Caller
,
BarProducer.ResultSink
geri arama. processResult
yeniden başlatıldıktan sonra
value
, bir hata oluşup oluşmadığını belirlemek için null. Bu, sıklıkla karşılaşılan bir davranıştır
bir alt görevden veya SkyValue aramasından çıktı kabul ettikten sonra tekrar aynı kalıbı kapatamaz.
acceptBarError
uygulandığında, sonucun
Hata baloncuklarının gerektirdiği şekilde Caller.ResultSink
.
Üst düzey StateMachine
için alternatifler Driver
s ve
SkyFunctions ile köprü oluşturma.
Hata işleme
Tasks.lookUp
ürününde hata işlemeyle ilgili birkaç örnek bulabilirsiniz.
geri çağırma işlevleri ve Değerleri
StateMachines
) İstisnalar,
InterruptedException
atılmaz, bunun yerine atlatılır
geri çağırmalar olabilir. Bu tür geri çağırmaların çoğu zaman özel veya anlamı vardır.
hatadan veya değerden tam olarak biri mi?
Bir sonraki bölümde Skyframe ile kurulan küçük ancak önemli bir etkileşim ele alacağız.
Hata kabarıklığı (--nokeep_continue)
Hata balonları oluşurken, istenenlerin tümü olmasa bile bir SkyFunction yeniden başlatılabilir
SkyValues kullanılabilir. Bu gibi durumlarda, sonraki durum hiçbir zaman
Tasks
API sözleşmesi nedeniyle ulaşıldı. Ancak StateMachine
,
istisnayı dağıtmaya devam eder.
Yayılım, sonraki duruma ulaşılıp ulaşılmadığına bakılmaksızın gerçekleşmesi gerektiği için,
geri çağırma hatası, bu görevi gerçekleştirmelidir. Dahili bir StateMachine
için,
Bunun için üst geri çağırma işlevi çağrılır.
SkyFunction ile arayüz oluşturan üst düzey StateMachine
'de bu komut,
ValueOrExceptionProducer
öğesinin setException
yöntemi çağrılarak yapılır.
Bu durumda ValueOrExceptionProducer.tryProduceValue
, istisnayı geçersiz kılacak olsa da
SkyValues eksik.
Driver
doğrudan kullanılıyorsa her zaman
makine tamamlanmamış olsa bile SkyFunction'dan hatalar yayıldı
işleniyor.
Etkinlik İşleme
Etkinlik yayınlaması gereken SkyFunctions için bir StoredEventHandler
eklenir
SkyKeyComputeState bölümüne ve daha fazla eklenerek StateMachine
gerekir. Geçmişte, Skyframe'in düşmesi nedeniyle StoredEventHandler
gerekiyordu
belirli etkinlikler yeniden oynatılmaz. Ancak bu sorun daha sonra giderilmiştir.
StoredEventHandler
ekleme işlemi, şunu basitleştirdiği için korunuyor:
geri çağırmaların işlenmesinden kaynaklanan etkinliklerin yayınlanması.
Driver
'ler ve SkyFunctions'a köprü
Driver
, StateMachine
öğelerinin yürütülmesini yönetmekten sorumludur.
belirtilen bir kök StateMachine
ile başlıyor. StateMachine
kullanıcıları
tekrarlayan bir şekilde alt görevleri sıraya koyun StateMachine
s, tek bir Driver
yönetebilir
alt göreve başladı. Bu alt görevler bir ağaç yapısı oluşturur.
Yapılandırılmış eşzamanlılık. Driver
, SkyValue'yu toplu olarak işler
alt görevler için arama
yaparak verimlilik artışı sağlar.
Aşağıdaki API'yi kullanarak Driver
etrafında oluşturulmuş çok sayıda sınıf bulunmaktadır.
public final class Driver {
public Driver(StateMachine root);
public boolean drive(SkyFunction.Environment env) throws InterruptedException;
}
Driver
, parametre olarak tek bir kök StateMachine
alır. Telefon etme
Driver.drive
, bir StateMachine
yönergesi olmadan mümkün olan en fazla şekilde StateMachine
yürütür.
Skyframe yeniden başlatıldı. StateMachine
tamamlandığında "true" (doğru), "false" (yanlış) değerini döndürür
aksi takdirde tüm değerlerin mevcut olmadığını gösterir.
Driver
, StateMachine
öğesinin eşzamanlı durumunu koruyor ve bu iyi bir değer
SkyKeyComputeState
dosyasına yerleştirilmeye uygundur.
Driver
öğesini doğrudan örneklendirme
StateMachine
uygulamaları, sonuçlarını geleneksel olarak
geri arama. Driver
,
örneği inceleyelim.
Driver
, ile birlikte SkyKeyComputeState
uygulamasına yerleştirilmiş.
biraz daha ileride tanımlanacak karşılık gelen ResultSink
uygulanması
aşağı tüketim. Üst düzeyde, State
nesnesi
Driver
tarihinde sona ereceği garanti edildiği için yapılan hesaplamadan kaynaklanan sonucuna varılmıştır.
class State implements SkyKeyComputeState, ResultProducer.ResultSink {
// The `Driver` instance, containing the full tree of all `StateMachine`
// states. Responsible for calling `StateMachine.step` implementations when
// asynchronous values are available and performing batched SkyFrame lookups.
//
// Non-null while `result` is being computed.
private Driver resultProducer;
// Variable for storing the result of the `StateMachine`
//
// Will be non-null after the computation completes.
//
private ResultType result;
// Implements `ResultProducer.ResultSink`.
//
// `ResultProducer` propagates its final value through a callback that is
// implemented here.
@Override
public void acceptResult(ResultType result) {
this.result = result;
}
}
Aşağıdaki kod ResultProducer
taslağını çiziyor.
class ResultProducer implements StateMachine {
interface ResultSink {
void acceptResult(ResultType value);
}
private final Parameters parameters;
private final ResultSink sink;
… // Other internal state.
ResultProducer(Parameters parameters, ResultSink sink) {
this.parameters = parameters;
this.sink = sink;
}
@Override
public StateMachine step(Tasks tasks) {
… // Implementation.
return this::complete;
}
private StateMachine complete(Tasks tasks) {
sink.acceptResult(getResult());
return DONE;
}
}
Daha sonra, sonucu geç hesaplama kodu aşağıdaki gibi görünebilir.
@Nullable
private Result computeResult(State state, Skyfunction.Environment env)
throws InterruptedException {
if (state.result != null) {
return state.result;
}
if (state.resultProducer == null) {
state.resultProducer = new Driver(new ResultProducer(
new Parameters(), (ResultProducer.ResultSink)state));
}
if (state.resultProducer.drive(env)) {
// Clears the `Driver` instance as it is no longer needed.
state.resultProducer = null;
}
return state.result;
}
Driver
öğe yerleştiriliyor
StateMachine
bir değer üretir ve hiçbir istisna sağlamazsa
Driver
, aşağıdaki örnekte gösterildiği gibi olası başka bir uygulamadır.
class ResultProducer implements StateMachine {
private final Parameters parameters;
private final Driver driver;
private ResultType result;
ResultProducer(Parameters parameters) {
this.parameters = parameters;
this.driver = new Driver(this);
}
@Nullable // Null when a Skyframe restart is needed.
public ResultType tryProduceValue( SkyFunction.Environment env)
throws InterruptedException {
if (!driver.drive(env)) {
return null;
}
return result;
}
@Override
public StateMachine step(Tasks tasks) {
… // Implementation.
}
SkyFunction aşağıdaki gibi koda sahip olabilir (State
işleve özgü SkyKeyComputeState
türü).
@Nullable // Null when a Skyframe restart is needed.
Result computeResult(SkyFunction.Environment env, State state)
throws InterruptedException {
if (state.result != null) {
return state.result;
}
if (state.resultProducer == null) {
state.resultProducer = new ResultProducer(new Parameters());
}
var result = state.resultProducer.tryProduceValue(env);
if (result == null) {
return null;
}
state.resultProducer = null;
return state.result = result;
}
Driver
özelliğinin StateMachine
uygulamasına yerleştirilmesi aşağıdakiler için daha uygundur:
Skyframe'in eşzamanlı kodlama stili.
İstisna oluşturabilecek StateMachines
Aksi takdirde, SkyKeyComputeState
-yerleştirilebilir ValueOrExceptionProducer
var
ve eşleştirilecek eşzamanlı API'lere sahip ValueOrException2Producer
sınıf
senkronize SkyFunction kodu.
ValueOrExceptionProducer
soyut sınıfı aşağıdaki yöntemleri içerir.
public abstract class ValueOrExceptionProducer<V, E extends Exception>
implements StateMachine {
@Nullable
public final V tryProduceValue(Environment env)
throws InterruptedException, E {
… // Implementation.
}
protected final void setValue(V value) { … // Implementation. }
protected final void setException(E exception) { … // Implementation. }
}
Yerleştirilmiş bir Driver
örneği içerir ve
Sürücü yerleştirme ve arayüzlerde ResultProducer
sınıfı
SkyFunction ile benzer şekilde ekleyebilirsiniz. Bir ResultSink
tanımlamak yerine,
uygulamaları, bunlardan biri gerçekleştiğinde setValue
veya setException
yöntemini çağırır.
Her ikisi de gerçekleştiğinde istisna, öncelik kazanır. tryProduceValue
yöntemi
eşzamansız geri çağırma kodunu eşzamanlı koda bağlar ve
istisna olarak çalışır.
Daha önce de belirtildiği gibi, hata şişirme sırasında bir hatanın
veya tüm girişler kullanılabilir olmadığından makine henüz bitmemiş olsa bile Alıcı:
buna dahil değilse tryProduceValue
, ayarlanmış tüm istisnaları,
üzerine konuşalım.
Son söz: Geri çağırmaları sonunda kaldırma
StateMachine
'lar, oldukça verimli ancak yoğun bir performans sunma yöntemidir.
eşzamansız hesaplama. Devamlılıklar (özellikle Runnable
biçiminde)
ListenableFuture
) Bazel kodunun belirli kısımlarında yaygın olarak kullanılır.
ancak SkyFunctions analizinde yaygın değildir. Analiz çoğunlukla CPU'ya bağlıdır ve
Disk G/Ç için verimli eşzamansız API yok. En sonunda bu,
geri arama optimizasyonu açısından iyidir çünkü bir öğrenme eğrisi
okunabilirlik.
En umut verici alternatiflerden biri, Java sanal iş parçacıklarıdır. Şunun yerine:
gerektiğinde, her şeyin yerini eşzamanlı veya düzgün çalışan engelleme
çağrısının en iyi yoludur. Bu, bir sanal iş parçacığı kaynağını (makine öğrenimini
platform iş parçacığının ucuz olması gerekir. Ancak sanal ileti dizilerinde bile
Basit eşzamanlı işlemleri iş parçacığı oluşturma ve senkronizasyon ile değiştirme
ilkel öğeler çok pahalıdır. StateMachine
sn. >
Java sanal iş parçacıkları büyüklükte daha yavaştı. Bu da
uçtan uca analiz gecikmesinde neredeyse 3 kat artış sağladı. Sanal iş parçacıkları
Önizleme özelliği olarak bilinen bu taşıma işleminin,
performansın arttığı daha ileri bir tarih olabilir.
Dikkate alınması gereken bir diğer yaklaşım da, herhangi bir sorunla karşılaşmadan Loom eş yordamlarını beklemektir. kullanılabilir hale gelir. Bunun avantajı, proje kapsamının senkronizasyon ek yüklerinden kurtulmanıza yardımcı olur.
Diğer tüm yöntemler başarısız olursa bayt kodunun düşük düzeyde yeniden yazılması da uygun olabilir. alternatifidir. Yeterli optimizasyonla, hedeflerinize ulaşmak için performanslarına genel bir bakış sağlar.
Ek
Cehennem Geri Çağırma
Geri çağırma cehennemi, eşzamansız kodda geri çağırmaların kullanıldığı, çok bilinen bir sorundur. Bu, sonraki bir adımın devamının, iç içe yerleştirilmiş bir adımın kontrol edin. Birçok adım varsa bu iç içe yerleştirme işlemi son derece derin. Kontrol akışıyla birleştirilirse kod yönetilemez hale gelir.
class CallbackHell implements StateMachine {
@Override
public StateMachine step(Tasks task) {
doA();
return (t, l) -> {
doB();
return (t1, l2) -> {
doC();
return DONE;
};
};
}
}
İç içe yerleştirilmiş uygulamaların avantajlarından biri, dış adım korunabilir. Java'da, yakalanan lambda değişkenleri bu nedenle bu tür değişkenleri kullanmak zahmetli olabilir. Derin iç içe yerleştirme: gibi lambdas yerine devamlılık olarak yöntem referansları döndürülmesiyle aşağıdaki gibi gösterilir.
class CallbackHellAvoided implements StateMachine {
@Override
public StateMachine step(Tasks task) {
doA();
return this::step2;
}
private StateMachine step2(Tasks tasks) {
doB();
return this::step3;
}
private StateMachine step3(Tasks tasks) {
doC();
return DONE;
}
}
runAfter
enjeksiyonu sırasında geri çağırma da gerçekleşebilir
desen çok yoğun şekilde kullanılıyor, ancak araya giren enjeksiyonlarla bu durum önlenebilir
emin olmanız gerekir.
Örnek: Zincirli SkyValue aramaları
Uygulama mantığı, çoğu zaman bağımlı zincirlere ihtiyaç duyar. SkyValue aramaları (örneğin, ikinci bir SkyKey ilk SkyValue'ya bağlıysa). Bunu çok nazik bir şekilde düşünerek, karmaşık bir veya geri çağırma yapısı vardır.
private ValueType1 value1;
private ValueType2 value2;
private StateMachine step1(...) {
tasks.lookUp(key1, (Consumer<SkyValue>) this); // key1 has type KeyType1.
return this::step2;
}
@Override
public void accept(SkyValue value) {
this.value1 = (ValueType1) value;
}
private StateMachine step2(...) {
KeyType2 key2 = computeKey(value1);
tasks.lookup(key2, this::acceptValueType2);
return this::step3;
}
private void acceptValueType2(SkyValue value) {
this.value2 = (ValueType2) value;
}
Ancak devamlılıklar yöntem referansları olarak belirtildiğinden, kod
durum geçişlerinde uygulanan prosedür: step2
, step1
öğesini izler. Burada,
lambda, value2
öğesini atamak için kullanılır. Bu, kodun sırasının
yukarıdan aşağıya doğru sıralaması.
Çeşitli İpuçları
Okunabilirlik: Yürütme Sırası
Okunabilirliği iyileştirmek için StateMachine.step
uygulamalarını korumaya çalışın
ve geri çağırma uygulamalarında, bunların hemen
gibi özellikler koda geçirilir. Kontrol akışı her zaman mümkün olmayabilir.
dalları. Bu gibi durumlarda ek yorumlar faydalı olabilir.
Örnek: Zincirli SkyValue aramaları, bir Bunun için ara yöntem referansı oluşturulur. Bu, küçük bir performansı artırmaya yardımcı oluyor. Bu da muhtemelen burada buna değer.
Kuşak Hipotez
Orta ömürlü Java nesneleri Java'nın nesiller arasındaki hipotezini çökertebilir
uzun süre yaşayan nesneleri işlemek için tasarlanmış
veya sonsuza kadar kalan nesnelerdir. Tanımı gereği,
SkyKeyComputeState
bu hipotezi çiğnemiştir. Şunları içeren bu tür nesneler:
kökleri Driver
olan, hâlâ çalışan tüm StateMachine
ağaçlarından oluşan bir ağaç
askıya alınırsa eşzamansız hesaplamaları bekleyerek bir orta yaşam süresi
tıklayın.
JDK19'da daha az kötü görünüyor ancak StateMachine
kullanırken bazen
önemli düşüşler olsa bile, GC süresinde bir artış gözlemleme
emin olun. StateMachine
ların orta yaşam ömrü olduğu için
eski nesile yükseltilebilir ve böylece daha hızlı bir şekilde doldurulur.
daha pahalı büyük veya tam GC'lerin temizlenmesine neden olabilir.
İlk önlem, StateMachine
değişkenlerinin kullanımını en aza indirmektir ancak
her zaman uygun değildir. Örneğin, birden fazla teklif stratejisi için bir değer
eyaletler. Mümkün olduğunda, yerel yığın step
değişkenleri genç nesildir
ve verimli bir şekilde GC'ye
dönüştürülmüştür.
StateMachine
değişken için, işlemleri alt görevlere ayırın ve takip edin
Bu örnekte, her bir satır öğesinde
StateMachine
olması da faydalıdır. Projenin gidişatı boyunca
kalıp takip edildiğinde yalnızca alt StateMachine
'lerin üst öğe referansı vardır
StateMachine
ve tersi değil. Bu, çocukların bir sonraki aşamaya geçip
sonuç geri çağırmaları kullanarak ebeveynleri güncellediğinde, çocuklar da
ve GC için uygun hale gelir.
Son olarak, bazı durumlarda, önceki eyaletlerde StateMachine
değişkeni gerekir
ancak sonraki eyaletlerde değil. Büyük boyutlu referansları geçersiz kılmak
ancak gerekli olmadığı bilindiğinde bu nesneleri.
Adlandırma eyaletleri
Bir yöntemi adlandırırken, çalışma için genellikle bir yöntem belirtmek mümkündür
bu sürecin kapsamına giriyor. Bunun nasıl yapılacağı da
StateMachine
var. Örneğin, foo
yönteminin mevcut olduğunu varsayalım
bir alt yöntemi bar
çağırır. Bu, StateMachine
dilinde şu dile çevrilebilir:
foo
durum dizisi ve ardından bar
. foo
artık bu davranışı içermiyor
bar
. Sonuç olarak, eyaletlere ilişkin yöntem adlarının kapsamı daha dar olma eğilimindedir.
yerel davranışı yansıtabilir.
Eşzamanlılık ağacı diyagramı
Aşağıda, Yapılandırılmış eşzamanlılık yazın. Bloklar küçük bir ağaç oluşturur.
-
Skyframe'in yeni baştan başlatma yaklaşımının aksine, değerleri kullanılamaz. ↩
-
step
adlı kişininInterruptedException
kullanmasına izin verildiğini, ancak örneklerde bu yoktur. Bazel kodunda, sonucu geçersiz kılan birkaç düşük yöntem vardır: veDriver
'a kadar yayılır (daha sonra açıklanacak şekilde,StateMachine
çalıştıran bir komut dosyası oluşturun. Bu e-postanın silineceğini beyan etmemek, gereksiz.↩ -
ConfiguredTargetFunction
, eşzamanlı alt görevleri motive etti. her bağımlılık için bağımsız işler yürütür. Üzerinde değişiklik yapmak yerine tüm bağımlılıkları aynı anda işleyen karmaşık veri yapılarını, Verimsizlikler ortaya çıktığında, her bağımlılığın kendi bağımsızStateMachine
↩ -
Tek bir adımda birden fazla
tasks.lookUp
çağrısı birlikte gruplandırılır. Ek toplu işlemler, eşzamanlı olarak yapılan aramalarla oluşturulabilir. alt görevler olabilir. ↩ -
Bu, kavram olarak Java'nın yapılandırılmış eşzamanlılığa benzer. jeps/428. ↩
-
Bunu, bir ileti dizisi oluşturup çok daha fazlasıdır. ↩