Suhteellisen uusi Mene ohjelmointikielelle istuu siististi keskellä maisemaa tarjoten paljon hyviä ominaisuuksia ja jättämällä tarkoituksella pois monia huonoja. Se kääntyy nopeasti, toimii nopeasti, sisältää ajonaikaisen ja roskakorin, sillä on yksinkertainen staattisen tyyppinen järjestelmä ja dynaamiset käyttöliittymät sekä erinomainen vakiokirjasto. Siksi niin monet kehittäjät haluavat oppia Go-ohjelmoinnin.
Sillä välin Golangilla on vahva kanta ominaisuuksiin, jotka voivat johtaa sekaannukseen ja virheisiin. Siinä jätetään pois OOP-idiomit, kuten perintö ja polymorfismi, koostumuksen ja yksinkertaisten rajapintojen hyväksi. Se vähentää poikkeusten käsittelyä paluuarvoissa olevien nimenomaisten virheiden hyväksi. Go-koodin asettamiseen on täsmälleen yksi oikea tapa, jonka gofmt
pakottaa työkalu. Ja niin edelleen.
Go on myös hyvä kieli kirjoittamiseen samanaikaisia ohjelmia : ohjelmat, joissa on paljon itsenäisesti käynnissä olevia osia. Ilmeinen esimerkki on verkkopalvelin: Jokainen pyyntö suoritetaan erikseen, mutta pyyntöjen on usein jaettava resursseja, kuten istuntoja, välimuistit tai ilmoitusjonot. Tämä tarkoittaa ammattitaitoiset Go-ohjelmoijat on käsiteltävä samanaikaista pääsyä kyseisiin resursseihin.
Vaikka Golangilla on erinomainen joukko matalan tason ominaisuuksia samanaikaisuuden käsittelemiseksi, niiden suora käyttö voi olla monimutkaista. Monissa tapauksissa kourallinen uudelleenkäytettäviä abstraktioita noilla matalan tason mekanismeilla tekee elämästä paljon helpompaa.
Tämänpäiväisessä Go-ohjelmointioppaassa tarkastellaan yhtä tällaista abstraktiota: Kääre, joka voi muuttaa minkä tahansa tietorakenteen kaupallinen palvelu . Käytämme Fund
tyyppi esimerkkinä - yksinkertainen myymälä käynnistysyrityksen jäljellä olevalle rahoitukselle, jossa voimme tarkistaa saldon ja tehdä nostoja.
Osoittaaksemme tämän käytännössä rakennamme palvelun pienin askelin, tekemällä sotkua matkan varrella ja siivoamalla sen sitten uudelleen. Go-opetusohjelmamme edetessä kohtaamme monia hienoja Go-kieliominaisuuksia, kuten:
Kirjoitetaan koodi seuratakseen startupin rahoitusta. Rahasto alkaa tietyllä saldolla, ja rahaa voidaan nostaa vain (tulot selvitetään myöhemmin).
Go on tarkoituksella ei olio-orientoitu kieli: Ei ole luokkia, objekteja tai perintöä. Sen sijaan ilmoitamme a rakennetyyppi kutsutaan Fund
, jolla on yksinkertainen toiminto uusien rahastorakenteiden luomiseen, ja kahdella julkisella menetelmällä.
fund.go
package funding type Fund struct { // balance is unexported (private), because it's lowercase balance int } // A regular function returning a pointer to a fund func NewFund(initialBalance int) *Fund { // We can return a pointer to a new struct without worrying about // whether it's on the stack or heap: Go figures that out for us. return &Fund{ balance: initialBalance, } } // Methods start with a *receiver*, in this case a Fund pointer func (f *Fund) Balance() int { return f.balance } func (f *Fund) Withdraw(amount int) { f.balance -= amount }
Seuraavaksi tarvitaan tapa testata Fund
Sen sijaan, että kirjoittaisimme erillisen ohjelman, käytämme Go-ohjelmaa testauspaketti , joka tarjoaa puitteet sekä yksikkötesteille että vertailuarvoille. Yksinkertainen logiikka Fund
-sivullamme ei todellakaan kannata kirjoittaa osuustestit, mutta koska puhumme paljon samanaikaisesta pääsystä rahastoon myöhemmin, vertailuarvon kirjoittaminen on järkevää.
Vertailuarvot ovat kuin yksikötestit, mutta sisältävät silmukan, joka suorittaa saman koodin monta kertaa (meidän tapauksessamme fund.Withdraw(1)
). Tämä antaa kehykselle ajan, kuinka kauan kukin iterointi kestää, keskiarvoistamalla ohimenevät erot levynhakemuksista, välimuistihäiriöistä, prosessin ajoituksista ja muista arvaamattomista tekijöistä.
Testauskehys haluaa, että jokainen vertailuarvo toimii vähintään yhden sekunnin ajan (oletusarvoisesti). Tämän varmistamiseksi se kutsuu vertailuarvoa useita kertoja, välittäen joka kerta kasvavan määrän iteraatioita (b.N
-kenttä), kunnes ajo kestää vähintään sekunnin.
Toistaiseksi vertailuarvomme vain tallettaa rahaa ja nostaa sen sitten dollarin kerrallaan.
fund_test.go
package funding import 'testing' func BenchmarkFund(b *testing.B) { // Add as many dollars as we have iterations this run fund := NewFund(b.N) // Burn through them one at a time until they are all gone for i := 0; i Suoritetaan nyt se:
$ go test -bench . funding testing: warning: no tests to run PASS BenchmarkWithdrawals 2000000000 1.69 ns/op ok funding 3.576s
Se meni hyvin. Suoritimme kaksi miljardia (!) Toistoa, ja saldon viimeinen tarkistus oli oikea. Voimme jättää huomiotta 'ei testejä suoritettavaksi' -varoituksen, joka viittaa yksikkötesteihin, joita emme kirjoittaneet (tämän opetusohjelman myöhemmissä Go-ohjelmointiesimerkeissä varoitus on katkaistu).
Samanaikainen käyttö Go-palvelussa
Tehdään nyt vertailuarvo samanaikaisesti mallinnamaan eri käyttäjiä, jotka tekevät nostoja samanaikaisesti. Tätä varten kutemme kymmenen gorutiinia ja annamme jokaiselle niistä kymmenesosan rahaa.

Goroutiinit ovat Go-kielen samanaikaisuuden peruselementti. He ovat vihreät langat - kevyet langat, joita hallinnoi Go-ajoaika, ei käyttöjärjestelmä. Tämä tarkoittaa, että voit suorittaa tuhansia (tai miljoonia) niitä ilman merkittäviä yleiskustannuksia. Goroutiineille syntyy go
avainsana ja aloita aina funktiolla (tai menetelmäpuhelulla):
// Returns immediately, without waiting for `DoSomething()` to complete go DoSomething()
Usein haluamme luoda lyhyen kertatoiminnon vain muutamalla rivillä koodia. Tässä tapauksessa voimme käyttää sulkua funktion nimen sijasta:
go func() { // ... do stuff ... }() // Must be a function *call*, so remember the ()
Kun kaikki gorutiinimme ovat syntyneet, tarvitsemme tapaa odottaa niiden päättymistä. Voisimme rakentaa sellaisen itse kanavia , mutta emme ole vielä kohdanneet niitä, joten se hyppää eteenpäin.
Toistaiseksi voimme käyttää vain WaitGroup
kirjoita Go: n vakiokirjastoon, joka on olemassa juuri tähän tarkoitukseen. Luomme sellaisen (nimeltään 'wg
') ja soitamme wg.Add(1)
ennen kutemaan kutakin työntekijää seuraamaan, kuinka monta työntekijää on. Sitten työntekijät raportoivat takaisin käyttämällä wg.Done()
. Sillä välin päägorutiinissa voimme vain sanoa wg.Wait()
estää, kunnes jokainen työntekijä on valmis.
Seuraavassa esimerkissä työntekijän gorutiinien sisällä käytämme defer
soittaa wg.Done()
.
defer
ottaa toiminnon (tai menetelmän) kutsun ja suorittaa sen välittömästi ennen nykyisen funktion paluuta, kun kaikki muu on tehty. Tämä on kätevä puhdistamiseen:
func() { resource.Lock() defer resource.Unlock() // Do stuff with resource }()
Tällä tavalla voimme helposti sovittaa Unlock
sen Lock
-sisällöllä luettavuuden takaamiseksi. Vielä tärkeämpää on, että lykätty toiminto suoritetaan vaikka vallitsisi paniikki päätoiminnossa (jotain, jonka voimme käsitellä kokeilemalla lopulta muilla kielillä).
Lopuksi, lykätyt toiminnot suoritetaan käänteinen järjestys, johon heidät kutsuttiin, mikä tarkoittaa, että voimme tehdä sisäkkäisen siivouksen hienosti (samanlainen kuin sisäkkäisten goto
s ja label
s, mutta paljon siistimpi C-idioomi):
func() { db.Connect() defer db.Disconnect() // If Begin panics, only db.Disconnect() will execute transaction.Begin() defer transaction.Close() // From here on, transaction.Close() will run first, // and then db.Disconnect() // ... }()
OK, joten kaikesta sanotusta tässä on uusi versio:
fund_test.go
package funding import ( 'sync' 'testing' ) const WORKERS = 10 func BenchmarkWithdrawals(b *testing.B) { // Skip N = 1 if b.N Voimme ennustaa, mitä täällä tapahtuu. Työntekijät kaikki teloittavat Withdraw
päällekkäin. Sen sisällä f.balance -= amount
lukee saldon, vähentää yhden ja kirjoittaa sen sitten takaisin. Mutta joskus kaksi tai useampia työntekijöitä molemmat lukevat saman tasapainon ja tekevät saman vähennyslaskun, ja lopputulos on väärä. Eikö?
$ go test -bench . funding BenchmarkWithdrawals 2000000000 2.01 ns/op ok funding 4.220s
Ei, se silti kulkee. Mitä täällä tapahtui?
Muista, että gorutiinit ovat vihreät langat - niitä hallinnoi Go-ajoaika, ei käyttöjärjestelmä. Ajonaikainen aikataulu laskee gorutiinit kaikilla käytettävissä olevilla käyttöjärjestelmäketjuilla. Tämän Go-kielen opetusohjelman kirjoittamisen aikana Go ei yritä arvata, kuinka monta käyttöjärjestelmän ketjua sen pitäisi käyttää, ja jos haluamme enemmän kuin yhden, meidän on sanottava niin. Lopuksi, nykyinen ajonaika ei estä gorutineja - gorutine jatkuu, kunnes se tekee jotain, mikä viittaa siihen, että se on valmis taukolle (kuten vuorovaikutuksessa kanavan kanssa).
Kaikki tämä tarkoittaa, että vaikka vertailuarvomme on nyt samanaikainen, se ei ole rinnakkain . Vain yksi työntekijöistämme juoksee kerrallaan, ja se toimii, kunnes se on valmis. Voimme muuttaa tätä kertomalla Go: lle käyttämään lisää ketjuja GOMAXPROCS
: n kautta ympäristömuuttuja.
$ GOMAXPROCS=4 go test -bench . funding BenchmarkWithdrawals-4 --- FAIL: BenchmarkWithdrawals-4 account_test.go:39: Balance wasn't zero: 4238 ok funding 0.007s
Tuo on parempi. Menetämme nyt ilmeisesti joitain nostojamme, kuten odotimme.

Tee siitä palvelin
Tässä vaiheessa meillä on useita vaihtoehtoja. Voisimme lisätä nimenomaisen mutex- tai luku- ja kirjoituslukon rahaston ympärille. Voisimme käyttää vertailua ja vaihtamista versionumeron kanssa. Voisimme mennä kaikki ulos ja käyttää a CRDT (ehkä korvaamalla balance
-kenttä kunkin asiakkaan tapahtumaluetteloilla ja laskemalla saldo niistä).
Mutta emme tee mitään näistä asioista nyt, koska ne ovat sotkuisia tai pelottavia tai molempia. Sen sijaan päätämme, että rahaston tulisi olla palvelin . Mikä palvelin on? Se on jotain mitä puhut. Go: ssa asiat puhuvat kanavien kautta.
Kanavat ovat perusviestintämekanismi gorutiinien välillä. Arvot lähetetään kanavalle (painamalla channel <- value
), ja ne voidaan vastaanottaa toiselle puolelle (value = <- channel
: lla). Kanavat ovat 'gorutine-turvallisia', mikä tarkoittaa, että mikä tahansa määrä gorutineja voi lähettää ja vastaanottaa heitä samanaikaisesti.
Puskurointi
Puskurointiviestintäkanavat voivat olla suorituskyvyn optimointi tietyissä olosuhteissa, mutta sitä tulisi käyttää erittäin huolellisesti (ja vertailemalla!).
Puskuroiduille kanaville on kuitenkin käyttötarkoituksia, jotka eivät liity suoraan viestintään.
Esimerkiksi yleinen kuristus-idiooma luo kanavan, jolla on (esimerkiksi) puskurikoko '10', ja lähettää sitten siihen välittömästi kymmenen tunnusta. Mikä tahansa määrä työntekijän gorutineja syntyy sitten, ja kukin saa kanavalta tunnuksen ennen työn aloittamista ja lähettää sen jälkeenpäin. Sitten, vaikka kuinka monta työntekijää onkin, vain kymmenen työskentelee koskaan samanaikaisesti.Oletuksena Go-kanavat ovat puskuroimaton . Tämä tarkoittaa, että arvon lähettäminen kanavalle estää, kunnes toinen gorutiini on valmis vastaanottamaan sen välittömästi. Go tukee myös kanavien kiinteitä puskurikokoja (käyttämällä make(chan someType, bufferSize)
). Normaalikäytössä tämä on kuitenkin yleensä huono idea .
Kuvittele rahastomme verkkopalvelin, jossa jokainen pyyntö tekee noston. Kun asiat ovat hyvin kiireisiä, FundServer
ei pysty pysymään mukana, ja komentokanavalle lähettämistä pyytävät pyynnöt alkavat estää ja odottaa. Siinä vaiheessa voimme pakottaa enimmäispyyntöjen määrän palvelimelle ja palauttaa järkevän virhekoodin (kuten 503 Service Unavailable
) asiakkaille, jotka ylittävät tämän rajan. Tämä on paras mahdollinen käyttäytyminen, kun palvelin on ylikuormitettu.
Puskurin lisääminen kanavillemme tekisi tästä käyttäytymisestä vähemmän determinististä. Voisimme helposti päätyä pitkiin jonoihin käsittelemättömiä komentoja, jotka perustuvat asiakkaan paljon aikaisemmin näkemiin tietoihin (ja ehkä pyyntöihin, jotka olivat sittemmin aikakatkaistut ylävirtaan). Sama pätee moniin muihin tilanteisiin, kuten vastapaineen asettamiseen TCP: n kautta, kun vastaanotin ei pysty pysymään lähettäjän mukana.
Joka tapauksessa Go-esimerkissämme pidämme kiinni puskuroimattomasta oletuskäyttäytymisestä.
Lähetämme komentoja kanavallamme FundServer
Jokainen vertailutyöntekijä lähettää komennot kanavalle, mutta vain palvelin saa ne.
Voisimme muuttaa rahastotyypistämme suoraan palvelintoteutuksen, mutta se olisi sotkuista - sekoittaisimme samanaikaisuuden käsittelyä ja liiketoimintalogiikkaa. Sen sijaan jätämme rahastotyypin sellaisenaan ja teemme FundServer
erillinen kääre sen ympärillä.
Kuten kaikilla palvelimilla, käärimellä on pääsilmukka, jossa se odottaa komentoja ja vastaa kuhunkin vuorotellen. Tässä on vielä yksi yksityiskohta, johon meidän on puututtava: komentojen tyyppi.

Osoittimet
Olisimme voineet saada komentokanavamme ottamaan * osoittimia * komentoihin (`chan * TransactionCommand`). Miksi emme?
Osoitteiden välittäminen gorutiinien välillä on riskialtista, koska jompikumpi gorutiini saattaa muuttaa sitä. Se on myös usein vähemmän tehokas, koska toinen gorutiini saattaa olla käynnissä eri CPU-ytimellä (mikä tarkoittaa enemmän välimuistin mitätöimistä).
Aina kun mahdollista, välitä mieluummin tavalliset arvot.Seuraavassa alla olevassa osiossa lähetämme useita erilaisia komentoja, joilla kaikilla on oma rakennetyyppinsä. Haluamme, että palvelimen komentokanava hyväksyy kaikki niistä. OOP-kielellä voimme tehdä tämän polymorfismin kautta: Pyydä kanavaa ottamaan superluokka, jonka yksittäiset komentotyypit olivat alaluokkia. Go: ssa käytämme rajapinnat sen sijaan.
Käyttöliittymä on joukko menetelmäallekirjoituksia. Kaikkia tyyppejä, jotka toteuttavat kaikki nämä menetelmät, voidaan käsitellä kyseisenä rajapintana (ilmoittamatta tekemään niin). Ensimmäisessä ajossa komentorakenteemme eivät todellakaan paljasta mitään menetelmiä, joten aiomme käyttää tyhjää käyttöliittymää interface{}
Koska sillä ei ole vaatimuksia, minkä tahansa arvo (mukaan lukien primitiiviset arvot, kuten kokonaisluvut) täyttää tyhjän käyttöliittymän. Tämä ei ole ihanteellinen - haluamme hyväksyä vain komentorakenteet - mutta palaamme siihen myöhemmin.
Aloitetaan toistaiseksi Go-palvelimen telineillä:
server.go
package funding type FundServer struct { Commands chan interface{} fund Fund } func NewFundServer(initialBalance int) *FundServer { server := &FundServer{ // make() creates builtins like channels, maps, and slices Commands: make(chan interface{}), fund: NewFund(initialBalance), } // Spawn off the server's main loop immediately go server.loop() return server } func (s *FundServer) loop() { // The built-in 'range' clause can iterate over channels, // amongst other things for command := range s.Commands { // Handle the command } }
Lisätään nyt komentoihin muutama Golang-rakennetyyppi:
type WithdrawCommand struct { Amount int } type BalanceCommand struct { Response chan int }
WithdrawCommand
sisältää vain nostettavan summan. Ei vastausta. BalanceCommand
ei ole vastausta, joten se sisältää kanavan sen lähettämistä varten. Tämä varmistaa, että vastaukset menevät aina oikeaan paikkaan, vaikka rahastomme myöhemmin päättäisi vastata epäkuntoon.
Nyt voimme kirjoittaa palvelimen pääsilmukan:
func (s *FundServer) loop() { for command := range s.Commands { // command is just an interface{}, but we can check its real type switch command.(type) { case WithdrawCommand: // And then use a 'type assertion' to convert it withdrawal := command.(WithdrawCommand) s.fund.Withdraw(withdrawal.Amount) case BalanceCommand: getBalance := command.(BalanceCommand) balance := s.fund.Balance() getBalance.Response <- balance default: panic(fmt.Sprintf('Unrecognized command: %v', command)) } } }
Hmm. Se on tavallaan ruma. Käynnistämme komentotyypin, käytämme tyyppiväitteitä ja mahdollisesti kaatumme. Edistetään joka tapauksessa ja päivitetään vertailuarvo palvelimen käyttöä varten.
func BenchmarkWithdrawals(b *testing.B) { // ... server := NewFundServer(b.N) // ... // Spawn off the workers for i := 0; i Se oli eräänlainen ruma, varsinkin kun tarkistimme tasapainon. Unohda koko juttu. Kokeillaan:
$ GOMAXPROCS=4 go test -bench . funding BenchmarkWithdrawals-4 5000000 465 ns/op ok funding 2.822s
Paljon parempi, emme enää menetä nostoja. Mutta koodia on vaikea lukea, ja on vakavampia ongelmia. Jos koskaan annamme BalanceCommand
ja unohda sitten lukea vastaus, rahastopalvelimemme estää ikuisesti yrittää lähettää sitä. Siivotaan asioita vähän.
Tee siitä palvelu
Palvelin on asia, jonka kanssa puhut. Mikä palvelu on? Palvelu on jotain, jonka kanssa puhut API: lla . Sen sijaan, että asiakaskoodi toimisi suoraan komentokanavan kanssa, teemme kanavasta viemättömän (yksityisen) ja kääritään käytettävissä olevat komennot toimintoihin.
type FundServer struct { commands chan interface{} // Lowercase name, unexported // ... } func (s *FundServer) Balance() int { responseChan := make(chan int) s.commands <- BalanceCommand{ Response: responseChan } return <- responseChan } func (s *FundServer) Withdraw(amount int) { s.commands <- WithdrawCommand{ Amount: amount } }
Nyt vertailuarvomme voi vain sanoa server.Withdraw(1)
ja balance := server.Balance()
, ja on vähemmän mahdollisuuksia vahingossa lähettää virheellisiä komentoja tai unohtaa lukea vastauksia.

Komennoille on vielä paljon ylimääräistä kattilaa, mutta palaamme siihen myöhemmin.
Tapahtumat
Lopulta rahat loppuvat aina. Sovitaan, että lopetamme nostamisen, kun rahastomme on laskenut viimeisen kymmenen dollarinsa, ja käytämme rahat yhteispizzaan juhlimaan tai nauttimaan. Vertailuarvomme heijastaa tätä:
// Spawn off the workers for i := 0; i Tällä kertaa voimme todella ennustaa tuloksen.
$ GOMAXPROCS=4 go test -bench . funding BenchmarkWithdrawals-4 --- FAIL: BenchmarkWithdrawals-4 fund_test.go:43: Balance wasn't ten dollars: 6 ok funding 0.009s
Olemme palanneet alkuun - useat työntekijät voivat lukea tasapainon kerralla ja sitten kaikki päivittää sen. Tämän käsittelemiseksi voisimme lisätä logiikkaa itse rahastoon, kuten minimumBalance
tai lisää toinen komento nimeltä WithdrawIfOverXDollars
. Nämä ovat molemmat kauheita ideoita. Sopimuksemme on keskuudessamme, ei rahaston omaisuus. Meidän tulisi pitää se sovelluslogiikassa.
Tarvitsemme todella liiketoimia , samassa mielessä kuin tietokantatapahtumat. Koska palvelumme suorittaa vain yhden komennon kerrallaan, tämä on erittäin helppoa. Lisätään Transact
komento, joka sisältää soittopyynnön (sulkeminen). Palvelin suorittaa kyseisen soittopyynnön oman ohjelmansa sisällä ja välittää raakan Fund
Soittopyyntö voi sitten tehdä turvallisesti mitä haluaa Fund
Semaforeja ja virheitä
Tässä seuraavassa esimerkissä teemme kaksi pientä asiaa väärin.
Ensinnäkin käytämme `Done`-kanavaa semaforina, jotta puhelukoodi tiedetään, kun sen tapahtuma on päättynyt. Se on hieno, mutta miksi kanavatyyppi on 'bool'? Lähetämme siihen vain 'totta' tarkoittamaan 'valmis' (mitä 'väärän' lähettäminen edes tarkoittaisi?). Haluamme todella yhden valtion arvon (arvon, jolla ei ole arvoa?). Go-tilassa voimme tehdä tämän käyttämällä tyhjää rakennetyyppiä: `struct {}`. Tämän etuna on myös vähemmän muistia. Tässä esimerkissä pidämme kiinni `boolista ', jotta emme näytä liian pelottavalta.
Toiseksi tapahtumamme takaisinsoitto ei palauta mitään. Kuten näemme hetkessä, voimme saada arvot takaisinsoittosta soittokoodiksi soveltamisalueen temppujen avulla. Todellisessa järjestelmässä tapahtuvat tapahtumat todennäköisesti epäonnistuvat joskus, joten Go-käytännön mukaan tapahtuma palauttaisi 'virheen' (ja tarkista sitten, onko se 'tyhjä' kutsukoodissa).
Emme tee sitä myöskään toistaiseksi, koska meillä ei ole virheitä. // Typedef the callback for readability type Transactor func(fund *Fund) // Add a new command type with a callback and a semaphore channel type TransactionCommand struct { Transactor Transactor Done chan bool } // ... // Wrap it up neatly in an API method, like the other commands func (s *FundServer) Transact(transactor Transactor) { command := TransactionCommand{ Transactor: transactor, Done: make(chan bool), } s.commands <- command <- command.Done } // ... func (s *FundServer) loop() { for command := range s.commands { switch command.(type) { // ... case TransactionCommand: transaction := command.(TransactionCommand) transaction.Transactor(s.fund) transaction.Done <- true // ... } } }
Tapahtumien takaisinkutsut eivät palauta suoraan mitään, mutta Go-kielellä on helppo saada arvoja suoraan sulkemisesta, joten teemme sen vertailuarvossa asettaaksesi pizzaTime
lippu, kun rahaa loppuu:
pizzaTime := false for i := 0; i Ja tarkista, että se toimii:
$ GOMAXPROCS=4 go test -bench . funding BenchmarkWithdrawals-4 5000000 775 ns/op ok funding 4.637s
Ei muuta kuin liiketoimet
Olet ehkä havainnut tilaisuuden puhdistaa asioita vielä lisää. Koska meillä on yleinen Transact
komentoa, emme tarvitse WithdrawCommand
tai BalanceCommand
enää. Kirjoitamme ne uudelleen tapahtumien muodossa:
func (s *FundServer) Balance() int { var balance int s.Transact(func(f *Fund) { balance = f.Balance() }) return balance } func (s *FundServer) Withdraw(amount int) { s.Transact(func (f *Fund) { f.Withdraw(amount) }) }
Ainoa komento, jonka palvelin ottaa, on TransactionCommand
, joten voimme poistaa koko interface{}
sotku sen toteutuksessa, ja pyydä sitä hyväksymään vain tapahtumakomennot:
type FundServer struct { commands chan TransactionCommand fund *Fund } func (s *FundServer) loop() { for transaction := range s.commands { // Now we don't need any type-switch mess transaction.Transactor(s.fund) transaction.Done <- true } }
Paljon parempi.
Tässä on viimeinen askel. Sen lisäksi, että se tarjoaa Balance
ja Withdraw
, palvelun toteutus ei ole enää sidottu Fund
Sen sijaan, että hallinnoi Fund
, se voisi hallita interface{}
ja käytetään käärimiseen mitä tahansa . Jokaisen tapahtuman soittopyynnön tulisi kuitenkin muuntaa interface{}
takaisin todelliseen arvoon:
type Transactor func(interface{}) server.Transact(func(managedValue interface{}) { fund := managedValue.(*Fund) // Do stuff with fund ... })
Tämä on rumaa ja altis virheille. Mitä todella haluamme, on kääntöaikaiset geneeriset aineet, joten voimme 'mallia' palvelimen tietylle tyypille (kuten *Fund
).
Valitettavasti Go ei tue geneerisiä valmisteita - vielä. Sen odotetaan saapuvan lopulta, kun joku selvittää järkevän syntaksin ja semantiikan sille. Sillä välin huolellinen käyttöliittymäsuunnittelu poistaa usein geneeristen lääkkeiden tarpeen, ja kun niitä ei ole, voimme tulla toimeen tyyppiväitteillä (jotka tarkistetaan ajon aikana).
Olemmeko valmiita?
Joo.
No, okei, ei.
Esimerkiksi:
-
Kaupan paniikki tappaa koko palvelun.
-
Aikakatkaisuja ei ole. Tapahtuma, joka ei koskaan palaa, estää palvelun ikuisesti.
-
Jos rahastomme kasvattaa uusia kenttiä ja tapahtuma kaatuu niiden päivityksen puolivälissä, tilamme on epäjohdonmukainen.
-
Tapahtumat voivat vuotaa hallitun Fund
esine, joka ei ole hyvä.
-
Ei ole mitään kohtuullista tapaa suorittaa transaktioita useilla rahastoilla (kuten nostaa yhdestä ja tallettaa toiseen). Emme voi vain sijoittaa tapahtumiamme, koska se sallisi umpikujat.
-
Tapahtuman suorittaminen asynkronisesti vaatii nyt uuden ohjelmiston ja paljon sotkua. Tähän liittyen haluamme todennäköisesti pystyä lukemaan uusimman Fund
valtio muualta, kun pitkäaikainen tapahtuma on käynnissä.
Seuraavassa Go-ohjelmointikielen opetusohjelmassa tarkastelemme joitain tapoja ratkaista nämä ongelmat.
Liittyvät: Hyvin jäsennelty logiikka: Golang OOP -opetusohjelma Perustietojen ymmärtäminen
Mikä on Go-kieli kirjoitettu?
Go-ohjelmointikielimääritys on englanniksi kirjoitettu asiakirja, kun taas Go: n vakiokirjasto ja kääntäjä on kirjoitettu itse Go-tiedostoon.
Mihin Goia käytetään?
Go on yleiskäyttöinen ohjelmointikieli ja sitä voidaan käyttää useisiin käyttötarkoituksiin. Go-sovellusta voidaan käyttää verkkopalvelimena taustapäässä, ja sitä on käytetty Dockerin, Kubernetesin ja Heroku-käyttöliittymän rakentamiseen.
Mitkä yritykset käyttävät Goia?
Go-palvelua käyttävät monet arvostetut teknologiayritykset, erityisesti Google, Dropbox, Docker, Kubernetes, Heroku ja monet muut.