Jumla ya sifa za ishara za Go ni msaada wa ndani kwa concurrency. Goroutines na njia ni primitives rahisi na ufanisi kwa maandishi ya mipango ya pamoja.
Lakini, kujaribu mipango ya pamoja inaweza kuwa ngumu na inaweza kuwa na makosa.
Katika Go 1.24, tunatoa kipimo kipya cha majaribio cha testing/synctest
ili kusaidia kujaribu msimbo wa pamoja.Utafutaji / Synctest
Katika Go 1.24, mfuko wa testing/synctest
ni wa majaribio na hauhusiani na ahadi ya ushirikiano wa Go. Haijulikani kwa kiwango cha chini. Ili kuitumia, unapaswa kuunda msimbo wako kwa kutumia GOEXPERIMENT=synctest
iliyowekwa katika mazingira yako.
Utafutaji / Synctest
Utafiti wa Utafiti wa Utafiti wa Utafiti wa Utafiti
Utafiti wa programu za pamoja ni vigumu
Kwa mwanzo, hebu tuangalie mfano rahisi.
Function ya context.AfterFunc
inapanga kazi ya kuitwa katika goroutine yake mwenyewe baada ya mazingira yamefutwa. Hapa ni mtihani wa uwezekano wa AfterFunc
:
Kifungu cha Mwisho
Kazi ya Kazi ya Kazi ya Kazi func TestAfterFunc(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) calledCh := make(chan struct{}) // kufungwa wakati AfterFunc inachukuliwa context.AfterFunc(ctx, func() { close(calledCh) }) // TODO: Tuhakikisha kwamba AfterFunc haijulikani. cancel() // TODO: Tuhakikisha kwamba AfterFunc imeitwa. }
func TestAfterFunc(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) calledCh := make(chan struct{}) // kufungwa wakati AfterFunc imeitwa context.AfterFunc(ctx, func() { close(calledCh) }) // TODO: Tumaini kwamba AfterFunc haikuitwa. cancel() // TODO: Tumaini kwamba AfterFunc imeitwa. }
Tunataka kuthibitisha hali mbili katika mtihani huu: kazi haijulikani kabla ya mtazamo kufutwa, na kazi ni imeitwa baada ya mtazamo kufutwa.
Mimi ni mwanachama wa
Tunaweza kujaribu kwa urahisi kwamba kazi haikujulikana kama yet, lakini jinsi ya kuthibitisha kwamba haikujulikana kama?
Kwa maana ya kuwa siwezi kufanya hivyo
Mfano wa kawaida ni kusubiri kwa muda fulani kabla ya kuhitimisha kwamba tukio litakuwa si kutokea.
// funcCalled inaelezea kama kazi ilichukuliwa. funcCalled := func() bool { select { case <-calledCh: return true case <-time.After(10 * time.Millisecond): return false } } kama funcCalled() { t.Fatalf("AfterFunc function called before context is canceled") } cancel() kama!funcCalled() { t.Fatalf("AfterFunc function is not called after context is canceled") }
// funcCalled inaelezea kama kazi ilichukuliwa. funcCalled := func() bool { select { case <-calledCh: return true case <-time.After(10 * time.Millisecond): return false } } kama funcCalled() { t.Fatalf("AfterFunc kazi ilichukuliwa kabla ya mazingira ni kufutwa") } cancel() kama!funcCalled() { t.Fatalf("AfterFunc kazi haijulikana baada ya mazingira ni kufutwa") }
Majaribio haya ni polepole: milliseconds 10 sio muda mwingi, lakini inajumuisha zaidi ya majaribio mengi.
Mtihani huu pia ni mbaya: milliseconds 10 ni muda mrefu kwenye kompyuta ya haraka, lakini sio kawaida kuona mapumziko ya sekunde kadhaa kwenye mifumo iliyoshirikiana na ya kujazwa CI.
WANA
Tunaweza kufanya mtihani kidogo kwa gharama ya kuifanya kuwa ngumu, na tunaweza kufanya yake kidogo kwa gharama ya kuifanya kuwa ngumu, lakini hatuwezi kufanya haraka na ya kuaminika.
Ufuatiliaji wa mfuko wa mtihani / synctest
Pakiti ya testing/synctest
inakuwezesha kuandika upya mtihani huu kuwa rahisi, haraka, na ya kuaminika, bila mabadiliko yoyote katika mtihani wa mtihani.
Utafutaji / Synctest
Pakiti hii ina kazi mbili tu: Kwenda
na Wasubiri
.
Kuanza kwa kutumia
Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini?
Run
huita kazi katika goroutine mpya. goroutine hii na goroutine yoyote iliyoanzishwa na hiyo inapatikana katika mazingira ya kujitenga ambayo tunajulikana kama bubble. Wait
inatarajia kila goroutine katika mpangilio wa goroutine ya sasa kuzuia goroutine nyingine katika mpangilio.
Kuanza kwa kutumia
Bubble kwa ajili yaMsimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini?
Tutaandika tena mtihani wetu hapo juu kwa kutumia mfuko wa testing/synctest
.
Utafutaji / Synctest
func TestAfterFunc(t *testing.T) { synctest.Run(func() { ctx, cancel := context.WithCancel(context.Background()) funcCalled := false context.AfterFunc(ctx, func() { funcCalled = true }) synctest.Wait() kama funcCalled { t.Fatalf("AfterFunc function called before context is canceled") } cancel() synctest.Wait() kama!funcCalled { t.Fatalf("AfterFunc function is not called after context is canceled") }) } func TestAfterFunc(t *testing.T) { synctest.Run(func() { ctx, cancel := context.WithCancel(context.Background()) funcCalled := false context.AfterFunc(ctx, func() { funcCalled = true }) synctest.Wait() kama funcCalled { t.Fatalf("AfterFunc function called before context is canceled") } cancel() synctest.Wait() kama!funcCalled { t.Fatalf("AfterFunc function is not called after context is canceled") } }Hii ni karibu sawa na mtihani wetu wa awali, lakini tumefunika mtihani katika simu ya synctest.Run
na tunaita synctest.Wait
kabla ya kudai kwamba kazi imeitwa au sio.
Kuanzishwa kwa Synctest.Run
Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini
Function ya Wait
inatarajia kila goroutine katika mpangilio wa wito wa kuzuia.Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini?
Utafiti huu sasa ni wa haraka na wa kuaminika.
Majaribio pia ni rahisi: tumebadilisha channel ya calledCh
na boolean. Kabla ya hapo tulihitaji kutumia channel ili kuepuka ushindani wa data kati ya goroutine ya mtihani na goroutine ya AfterFunc
, lakini kazi ya Wait
sasa hutoa usindikaji huo.
Mifano ya Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha Kifungu cha 1.Kazi ya Kazi ya Kazi ya Kazi Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini?
Detector ya mashindano anajua wito wa Wait
, na mtihani huu unatolewa wakati unaendesha na -race
. Ikiwa tunapoteza wito wa pili wa Wait
, detector ya mashindano itatoa taarifa sahihi ya mashindano ya data katika mtihani.
Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini?Kazi ya Kazi ya Kazi ya Kazi ya KaziMsimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini?Utafiti wa wakati
Msimbo wa ushindani mara nyingi unashughulikia wakati.
Utafiti wa msimbo unaofanya kazi na muda inaweza kuwa vigumu. Matumizi ya wakati halisi katika majaribio hufanya majaribio ya polepole na mabaya, kama tulivyoona hapo juu. Matumizi ya wakati wa uongo inahitaji kuepuka kazi za mfuko wa time
, na kubuni msimbo unaofanya majaribio ili kufanya kazi na muda wa uongo wa chaguo.
Kazi ya Kazi ya Kazi ya Kazi ya Kazi
Pakiti ya testing/synctest
inafanya kuwa rahisi kujaribu msimbo unaotumia muda.
Utafutaji / Synctest
Goroutines katika mpangilio ulianza na Run
kutumia wakati wa uongo. Kwenye mpangilio, kazi katika mfuko wa time
hufanya kazi kwenye wakati wa uongo.Kuanza kwa kutumia
Kazi ya Kazi ya Kazi ya Kazi ya Kazi
Kwa kuonyesha, hebu tuandikishe mtihani kwa context.WithTimeout
kazi. WithTimeout
huunda mtoto wa mazingira, ambayo huishia baada ya muda uliopangwa.
Kuanzishwa kwa context.WithTimeout
Kuanzishwa kwa WithTimeoutKuanzisha mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juuKuanzisha mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juu ya mlinzi juuTunaandika mtihani huu kama tulivyofanya kazi kwa wakati halisi. tofauti pekee ni kwamba tunashughulikia kazi ya mtihani katika synctest.Run
, na kuita synctest.Wait
baada ya kila mtihani wa time.Sleep
kusubiri wakati wa mfuko wa mazingira kuishia.
Kuanzishwa kwa Synctest.Run
Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni ninimfano wa wakati.Sleep
Blocking na mpira
Mfano muhimu katika testing/synctest
ni mpira kuwa kufungwa kwa muda mrefu. Hii hutokea wakati kila goroutine katika mpira ni kufungwa, na inaweza tu kufungwa na goroutine mwingine katika mpira.
Utafutaji / Synctest
kuzuiliwa kwa muda mrefu
Ikiwa mpira unafungwa kwa muda mrefu:
- Ikiwa kuna wito wa nje wa
Wait
, unarejeshwa. - Kama sivyo, muda unakwenda hadi wakati ujao ambao unaweza kufuta goroutine, ikiwa kuna.
- Kama sivyo, mpira unashindwa na
Run
huwa na hofu.
Ikiwa kuna wito wa nje wa Wait
, unarejeshwa. Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini?Vinginevyo, muda unakwenda hadi wakati ujao ambao unaweza kufungua goroutine, ikiwa kuna. Vinginevyo, mpangilio unafungwa na Kwenda
panic. Kuanza kwa kutumia
Bubble si kuzuiliwa kwa muda mrefu kama goroutine yoyote ni kuzuiliwa lakini inaweza kukumbukwa na tukio fulani kutoka nje ya bubble.
Orodha kamili ya vitendo ambavyo vinafungua goroutine kwa muda mrefu ni:
- a kutuma au kupokea kwenye channel nil
- a kutuma au kupokea imefungwa kwenye channel iliyoundwa ndani ya mpangilio huo
- a maelezo ya kuchagua ambapo kila kesi ni kudumu kuzuia
time.Sleep
sync.Cond.Wait
sync.WaitGroup.Wait
a kutuma au kupokea kwenye channel ya nil a kutuma au kupokea imefungwa kwenye channel iliyoundwa ndani ya mpira huo mstari uliochaguliwa ambapo kila kesi ni kudumu kuzuia mfano.mfano
mfanomfano wa wakati.Sleep
Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini?Sync.Cond.Wait
kwa ajili yamfano.Mfano.Mfano
mfano.Mfano.mfano.mfano.mfanoMfano wa Mifano ya Mifano
Utaratibu juu ya sync.Mutex
si kuzuia kwa muda mrefu.
Sync.Mutex
kwa ajili ya
Ni kawaida kwa majukumu ya kupata mutex ya kimataifa. Kwa mfano, majukumu kadhaa katika pakiti ya kutafakari hutumia cache ya kimataifa iliyohifadhiwa na mutex. Ikiwa goroutine katika sinctest bubble inashindwa wakati unapata mutex iliyohifadhiwa na goroutine nje ya bubble, haina kuzuiliwa kwa muda mrefu - inachukuliwa, lakini itafungwa na goroutine kutoka nje ya bubble yake.
Kwa sababu mutexes kwa kawaida si kuhifadhi kwa muda mrefu, sisi tu kuzuia yao kutoka testing / synctest
ya kuzingatia.
Utafutaji / Synctest
Mipango ya Mipango ya Mipango
Mipango yaliyoundwa ndani ya mpangilio hufanya tofauti na yale yaliyoundwa nje.
Mafunzo ya channel ni kuzuia kwa muda mrefu tu ikiwa channel ni bubbled (kuumbwa katika bubble).
sheria hizi zinahakikisha kwamba goroutine ni kuzuiliwa kwa muda mrefu tu wakati wa kuwasiliana na goroutines ndani ya mpangilio wake.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >Utaratibu wa nje wa I/O, kama vile kusoma kutoka kwa uhusiano wa mtandao, hauwezi kuzuia kwa muda mrefu.
Ujumbe wa mtandao unaweza kufutwa na maandishi kutoka nje ya figo, labda hata kutoka kwa mchakato mwingine. Hata kama mwandishi pekee wa uhusiano wa mtandao pia ni katika figo moja, muda wa kuendesha hauwezi kutambua kati ya uhusiano unao kusubiri data zaidi kuja na moja ambapo kernel imepokea data na ni katika mchakato wa kuwasilisha.
Kwa mfano, kazi ya net.Pipe
huunda wanandoa wa net.Conn
ambazo hutumia uhusiano wa mtandao katika kumbukumbu na zinaweza kutumika katika majaribio ya synctest.
pipe ya mtandao
Mifumo ya Kijamii.Conn
Uhai wa Bubble
Function ya Run
huanza goroutine katika mpangilio mpya. Inarudi wakati kila goroutine katika mpangilio umeondoka. Inasababisha hofu ikiwa mpangilio unafungwa kwa muda mrefu na haiwezi kufungwa kwa kuendeleza muda.
Kuanza kwa kutumia
Uhitaji kwamba kila goroutine katika kuondoka bubble kabla ya Run kurudi inamaanisha kwamba majaribio lazima kuwa makini kusafisha goroutine yoyote ya nyuma kabla ya kukamilisha.
Utafiti wa msimbo wa mtandao
Tazama mfano mwingine, wakati huu kutumia mfuko wa testing/synctest
kujaribu mpango wa mtandao.Utafutaji / Synctest
Mfumo wa HTTP / HTTP / HTTP / HTTP
Mteja wa HTTP anayeuliza ombi anaweza kuwa na kichwa cha "Tazama: 100-kuendelea" ili kumwambia seva kwamba mteja ana data ya ziada ya kutuma.Mteja anaweza kisha kujibu na jibu la habari ya kuendelea 100 ili kumwomba ombi jingine, au na hali nyingine ili kumwambia mteja kwamba maudhui hayahitajika. Kwa mfano, mteja anayetumia faili kubwa anaweza kutumia kipengele hiki ili kuthibitisha kwamba server ana nia ya kukubali faili kabla ya kutuma.
Test yetu itathibitisha kuwa wakati wa kutuma kichwa cha "Tazama: 100-kuendelea" mteja wa HTTP haitawasilisha maudhui ya ombi kabla ya seva inahitaji, na kwamba inatuma maudhui baada ya kupokea jibu la kuendelea 100.
Mara nyingi majaribio ya mteja na seva ya mawasiliano yanaweza kutumia uhusiano wa mtandao wa loopback. Wakati wa kufanya kazi na testing/synctest
, hata hivyo, kwa kawaida tunataka kutumia uhusiano wa mtandao wa uongo ili kuruhusu kutambua wakati goroutines zote zimefungwa kwenye mtandao. Tutaanza mtihani huu kwa kuunda http.Transport
(mteja wa HTTP) ambayo hutumia uhusiano wa mtandao uliotengenezwa na net.Pipe
.
Utafutaji / Synctest
HTTP.Hifadhi ya Usalama
pipe ya mtandao
func Test(t *testing.T) { synctest.Run(func() { srvConn, cliConn := net.Pipe() defer srvConn.Close() defer cliConn.Close() tr := &http.Transport{ DialContext: func(ctx context.Context, mtandao, mstari wa anwani) (net.Conn, makosa) { return cliConn, nil }, // kuweka timeout isiyo ya null inawezesha "Expect: 100-continue" usindikaji. // Kwa sababu mtihani ufuatao hauwezi kusimama, // hatuwezi kukutana na timeout hii, // hata kama mtihani unachukua muda mrefu kuendesha kwenye mashine ya polepole.func Test(t *testing.T) { synctest.Run(func() { srvConn, cliConn := net.Pipe() defer srvConn.Close() defer cliConn.Close() tr := &http.Transport{ DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { return cliConn, nil }, // Seting a non-zero timeout enables "Expect: 100-continue" handling. // Kwa kuwa mtihani ufuatao hauwezi kulala, // hatuwezi kukutana na hii timeout, // hata kama mtihani unachukua muda mrefu kuendesha kwenye mashine ya polepole.
Sisi kutuma ombi juu ya usafirishaji huu na mkusanyiko wa kichwa "Hata: 100 kuendelea." ombi inatumwa katika goroutine mpya, kwa sababu haitakuwa kamili mpaka mwisho wa mtihani.
mwili := "kuomba mwili" go func() { req, _ := http.NewRequest("PUT", "http://test.tld/", strings.NewReader(body)) req.Header.Set("Uhitaji", "100-kuendelea") resp, err := tr.RoundTrip(req) kama err!= nil { t.Errorf("RoundTrip: makosa isiyotarajiwa %v", err) } else resp {.Body.Close() }()
mwili := "kuomba mwili" go func() { req, _ := http.NewRequest("PUT", "http://test.tld/", strings.NewReader(body)) req.Header.Set("Uhitaji", "100-kuendelea") resp, err := tr.RoundTrip(req) kama err!= nil { t.Errorf("RoundTrip: makosa ya kutokuwa na matarajio %v", err) } mwingine { resp.Body.Close() } }()
Tunaweza kusoma vichwa vya maombi vilivyotumwa na mteja.
req, err := http.ReadRequest(bufio.NewReader(srvConn)) kama err!= nil { t.Fatalf("ReadRequest: %v", err) }
req, err := http.ReadRequest(bufio.NewReader(srvConn)) kama err!= nil { t.Fatalf("ReadRequest: %v", err) }
Sasa tunakuja kwenye moyo wa mtihani.Tunataka kuthibitisha kwamba mteja hatutuma mwili wa ombi bado.
Tutaanza goroutine mpya kwa kurekodi mwili uliotumwa kwa seva katika strings.Builder
, kusubiri goroutine zote katika mpangilio wa kuzuia, na kuthibitisha kwamba hatuna kusoma chochote kutoka kwa mwili bado.
Mifumo ya Mifumo ya Strings.Builder
Ikiwa tunasahau wito wa synctest.Wait
, detector ya mashindano itatoa malalamiko sahihi kuhusu mashindano ya data, lakini kwa Wait
hii ni salama.
Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni niniMsimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni nini? var gotBody strings.Builder go io.Copy(&gotBody, req.Body) synctest.Wait() kama got := gotBody.String(); got!= "" { t.Fatalf("kabla ya kutuma 100 kuendelea, kusoma mwili: %q", got) }
var gotBody strings.Builder go io.Copy(&gotBody, req.Body) synctest.Wait() kama got := gotBody.String(); got!= "" { t.Fatalf("kabla ya kutuma 100 kuendelea, kusoma mwili: %q", got) }
Tunaandika jibu la "100 kuendelea" kwa mteja na kuthibitisha kwamba sasa inatuma mwili wa ombi.
srvConn.Write([]byte("HTTP/1.1 100 Continue\r\r\n\n")) synctest.Wait() kama got := gotBody.String(); got!= mwili { t.Fatalf(" baada ya kutuma 100 kuendelea, kusoma mwili %q, wanataka %q", got, mwili) }
srvConn.Write([]byte("HTTP/1.1 100 Continue\r\n\n\n")) synctest.Wait() kama got := gotBody.String(); got!= mwili { t.Fatalf(" baada ya kutuma 100 kuendelea, kusoma mwili %q, wanataka %q", got, mwili) }
Na hatimaye, sisi kumaliza kwa kutuma "200 OK" jibu ili kukamilisha ombi.
Tumeanza goroutines kadhaa wakati wa mtihani huu. Wito wa synctest.Run
utasubiri kwa wote wao kuondoka kabla ya kurudi.
Kuanzishwa kwa Synctest.Run
srvConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n") }) }
msongamano.Weka([]byte("HTTP/1.1 200 OK\r\n\r\n") }) }
Majaribio haya yanaweza kupanuliwa kwa urahisi ili kujaribu tabia zingine, kama vile kuthibitisha kwamba mwili wa ombi haukutumwa ikiwa seva haina kuuliza, au kwamba inatumwa ikiwa seva haina kujibu ndani ya muda.
Hatua ya majaribio
Tunaanzisha testing/synctest
katika Go 1.24 kama mfuko wa majaribio. Kulingana na maoni na uzoefu tunaweza kuchapisha na au bila mabadiliko, kuendelea majaribio, au kuondoa katika toleo la baadaye la Go.
Utafutaji / Synctest
Kazi ya Utafiti
Paketi haiwezi kuonekana kwa default. kuitumia, fanyeni msimbo wako na GOEXPERIMENT=synctest
iliyoundwa katika mazingira yako.
Utafiti wa Utafiti wa Utafiti wa Utafiti wa Utafiti
Tunataka kusikia maoni yako! Ikiwa unajaribu testing/synctest
, tafadhali ripoti uzoefu wako, chanya au hasi, kwenye go.dev/issue/67434.
Utafutaji / Synctest
go.dev/issue/67434kwa hrsMshahara: Mshahara ya kiroho
Msimamizi Mjerumani akamwauliza kijana mwenyeji je jina la mahali ni niniDamien Neil
Picha kutoka kwa Gabriel Gusmao juu ya Uplash
Picha ya Gabriel Gusmao juu ya UnsplashJuz.Unsplash
Makala hii inapatikana kwenye Blog ya Go kwa leseni ya CC BY 4.0 DEED.
Makala hii inapatikana kwenye Blog ya Go kwa leseni ya CC BY 4.0 DEED.
Baba wa Mtandao Baba wa Mtandao