A współprogram Jest podobny do wątku, jest linią wykonawczą z własnym stosem, własnymi zmiennymi lokalnymi i własnym wskaźnikiem do instrukcji, ale ze szczególną cechą współdzielenia zmiennych globalnych i innych elementów z innymi współprogramami.
Ale musimy wyjaśnić, że istnieją różnice między wątki i współprogramy, główna różnica polega na tym, że program używający wątków uruchamia je jednocześnie, współprogramy z drugiej strony są one oparte na współpracy, gdzie program używający współprogramów uruchamia tylko jeden z nich, a zawieszenie ich jest osiągane tylko wtedy, gdy jest to wyraźnie wymagane.
ten współprogramy Są niezwykle potężne, zobaczmy, co obejmuje ta koncepcja i jak możemy je wykorzystać w naszych programach.
Podstawowe koncepcje
Wszystkie funkcje związane z współprogramami w Lua znajdują się w tabeli współprogramów, gdzie funkcja Stwórz () pozwala nam je tworzyć, ma prosty argument i jest funkcją z kodem, który będzie wykonywał współprogram, gdzie jego zwrotem jest wartość typu wątku, który reprezentuje nowy współprogram. Nawet argument do stworzenia współprogramu jest czasami funkcją anonimową, jak w poniższym przykładzie:
co = coroutine.create (function () print ("Hello Solvetic") end)A współprogram może mieć cztery różne stany:
- zawieszony
- w pośpiechu
- nie żyje
- normalna
Kiedy go tworzymy, zaczyna się w stanie przerwane, co oznacza, że współprogram nie uruchamia się automatycznie, gdy jest tworzony po raz pierwszy. Status współprogramu można sprawdzić w następujący sposób:
drukuj (współprogram.status (co))Gdzie, aby móc uruchomić nasz współprogram wystarczy skorzystać z funkcji podsumowuje(), to, co robi wewnętrznie, to zmiana statusu z zawieszonego na uruchomiony.
współrutyna.życiorys (co)Jeśli złożymy cały nasz kod razem i dodamy dodatkową linię, aby zapytać o dodatkowy status naszego współprogramu po wykonaniu podsumowuje widzimy wszystkie stany, przez które przechodzi:
co = coroutine.create (function () print ("Hello Solvetic") end) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))Wchodzimy do naszego terminala i uruchamiamy nasz przykład, zobaczmy wyjście naszego programu:
lua coroutines1.lua wątek: 0x210d880 Zawieszony Hello Solvetic martwyJak widać pierwsze wrażenie współprogramu to wartość nici, to mamy stan zawieszony, i to jest w porządku, ponieważ jest to pierwszy stan podczas tworzenia, a następnie z podsumowuje Uruchamiamy współprogram, za pomocą którego drukuje wiadomość, a następnie jej status to nie żyjejak wypełniła swoją misję.
Współprogramy na pierwszy rzut oka mogą wydawać się skomplikowanym sposobem wywoływania funkcji, jednak są one znacznie bardziej złożone. Siła tego samego tkwi w dużej części funkcji dawać () która pozwala zawiesić uruchomioną współprogramę w celu późniejszego wznowienia jej działania, zobaczmy przykład użycia tej funkcji:
co = coroutine.create (function () for i = 1.10 do print ("podsumowanie współprogramu", i) coroutine.yield () end end) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co ) coroutine .wznawianie (co)Ta funkcja będzie działać do pierwszego dawaći niezależnie od tego czy mamy cykl dla, będzie drukować tylko według tak wielu podsumowuje Zajmijmy się naszym współprogramem, na koniec spójrzmy na wyjście przez terminal:
współprogramy lua 1. lua 1 2 3 4To byłoby wyjście przez terminal.
Filtry
Jednym z najjaśniejszych przykładów wyjaśniających współprogramy jest przypadek konsument Tak generator informacji. Załóżmy więc, że mamy funkcję, która w sposób ciągły generuje pewne wartości z odczytu pliku, a następnie mamy inną funkcję, która je odczytuje, zobaczmy przykład ilustrujący, jak te funkcje mogą wyglądać:
generator funkcji () while true wykonaj local x = io.read () send (x) end end funkcja consumer () while true wykonaj local x = odbieraj () io.write (x, "\ n") end endW tym przykładzie zarówno konsument, jak i generator działają bez żadnego odpoczynku i możemy je zatrzymać, gdy nie ma więcej informacji do przetworzenia, jednak problem polega na tym, jak zsynchronizować funkcje Wysłać() Tak odbierać(), ponieważ każdy z nich ma swoją własną pętlę, a drugi z założenia jest usługą wywoływaną.
Ale dzięki współprogramom ten problem można rozwiązać szybko i łatwo, korzystając z podwójnej funkcji wznowić / wydajność możemy sprawić, by nasze funkcje działały bez problemu. Kiedy współprogram wywołuje funkcję dawać, nie wprowadza nowej funkcji, ale zwraca wywołanie, które jest w toku i które może wyjść z tego stanu tylko za pomocą wznowienia.
Podobnie, dzwoniąc podsumowuje nie uruchamia również nowej funkcji, zwraca wywołanie oczekiwania do dawać, podsumowując ten proces, musimy zsynchronizować funkcje Wysłać() Tak odbierać(). Stosując tę operację, musielibyśmy użyć odbierać() Zastosować podsumowuje do generatora, aby wygenerować nowe informacje, a następnie Wysłać() zastosować dawać Dla konsumenta zobaczmy jak wyglądają nasze funkcje z nowymi zmianami:
function odbiera () status lokalny, value = coroutine.resume (generator) zwraca wartość end function send (x) coroutine.yield (x) end gen = coroutine.create (function () podczas gdy true do local x = io.read () wyślij (x) koniec koniec)Ale nadal możemy dalej ulepszać nasz program, i to za pomocą filtry, które są zadaniami, które pełnią funkcję generatorów i konsumentów, jednocześnie dokonując bardzo ciekawego procesu transformacji informacji.
A filtr może zrobić? podsumowuje z generatora, aby uzyskać nowe wartości, a następnie zastosować dawać do przekształcania danych dla konsumenta. Zobaczmy, jak możemy łatwo dodać filtry do naszego poprzedniego przykładu:
gen = generator () fil = filtr (gen) konsument (fil)Jak widać było to niezwykle proste, gdzie oprócz optymalizacji naszego programu zdobyliśmy punkty w czytelności, ważne dla przyszłego utrzymania.
Corrouines jako iteratory
Jednym z najjaśniejszych przykładów generatora / konsumenta jest iteratory obecne w cyklach rekurencyjnych, w których iterator generuje informacje, które zostaną wykorzystane przez ciało w cyklu rekurencyjnym, więc nie byłoby nierozsądne używanie współprogramów do pisania tych iteratorów, nawet współprogramy mają specjalne narzędzie do tego zadania.
Aby zilustrować użycie, jakie możemy zrobić współprogramy, napiszemy iterator generujący permutacje danej tablicy, czyli umieszczać każdy element tablicy na ostatniej pozycji i odwracać go, a następnie rekurencyjnie generować wszystkie permutacje pozostałych elementów, zobaczmy jak nasze oryginalna funkcja byłaby bez współprogramów:
funkcja print_result (var) for i = 1, #var do io.write (var [i], "") end io.write ("\ n") endTeraz całkowicie zmieniamy ten proces, najpierw zmieniamy print_result () według plonów, zobaczmy zmianę:
function permgen (var1, var2) var2 = var2 or # var1 if var2 <= 1 then coroutine.yield (var1) elseJest to jednak przykład ilustrujący, jak działają iteratory Lua udostępnia nam funkcję o nazwie zawinąć który jest podobny do StwórzJednak nie zwraca współprogramu, zwraca funkcję, która po wywołaniu podsumowuje współprogram. Następnie użyć zawinąć powinniśmy używać tylko:
permutacje funkcji (zmienna) return coroutine.wrap (funkcja () permgen (zmienna) end) endZwykle ta funkcja jest znacznie łatwiejsza w użyciu niż Stwórz, ponieważ daje nam dokładnie to, czego potrzebujemy, czyli podsumowując, jest jednak mniej elastyczny, ponieważ nie pozwala nam zweryfikować statusu współprogramu utworzonego za pomocą zawinąć.
Współprogramy w Lua Są niezwykle potężnym narzędziem do radzenia sobie ze wszystkim, co jest związane z procesami, które muszą być wykonywane ręka w rękę, ale czekając na zakończenie przez osobę, która dostarcza informacje, możemy również zobaczyć ich zastosowanie do rozwiązywania złożonych problemów związanych z procesami generatora / konsumenta a także optymalizację konstrukcji iteratorów w naszych programach.