Sei sulla pagina 1di 14

Esempi di programmazione concorrente, deadlock e gestione memoria

1) Definire i seguenti problemi della programmazione concorrente


Mutua esclusione. Oltre alla sua definizione, elencare le possibili soluzioni con eventuali
pseudocodici
Determinatezza. Oltre alla definizione, dare un cenno alla descrizione analitica del problema
Deadlock. Oltre alla sua descrizione, dare le quattro condisioni necessarie e le possibili
soluzioni al problema

La Mutua Esclusione riguarda le situazioni per cui bisogna impedire che due o pi processi
operino contemporaneamente sui dati condivisi.
Possibili soluzioni: soluzioni software (variabili di blocco), soluzioni hardware (istruzioni
atomiche), primitive di nucleo (ad esempio semafori), soluzioni linguistiche (monitor).
Possibili pseudocodici delle soluzioni software sono :
processo1{
while (true){
Sezione_noncritica;
while(turno==1);
Sezione critica1;
turno=1;
}
}

processo2{
while (true){
Sezione_noncritica;
while(turno==2);
Sezione critica2;
turno=2;
}
}

e lalgoritmo di Peterson:
processo1{
while (true){
Sezione_noncritica;
a=0; t=1;
while(b==0 &&
t==1);
Sezione critica1;
a=1;
}
}

processo2{
while (true){
Sezione_noncritica;
b=0; t=2;
while(a==0 &&
t==2);
Sezione critica2;
b=1;
}
}

La Determinatezza riguarda il fatto che tutte le diverse sequenze di esecuzione di un


processo concorrente multitasking portino allo stesso risultato.
La determinatezza si pu descrivere come segue:
Se V(Mi,) la sequenza di valori scritti dalla sequenza di esecuzione in Mi, allora un
sistema determinato se per ogni stato iniziale, V(Mi, ) = V(Mi, ) per ogni sequenza di
esecuzione , .
Il Deadlock rappresenta la situazione secondo la quale almeno due processi concorrenti
restano sospesi per un tempo infinito per via di richieste a risorse che richiedono un
accesso esclusivo. Quattro condizioni necessarie per il deadlock sono:
a. mutua esclusione (esistono risorse condivise che sono utilizzate in mutua
esclusione)

b. hold&wait (un processo, una volta acquisita una risorsa, la mantiene in


modo esclusivo e ne richiede delle altre)
c. le risorse non possono essere sottratte (no-preemption)
d. attesa circolare.
Possibili soluzioni al problema sono: prevenire lo stallo (negare una delle condizioni
necessarie), evitare lo stallo (passaggio attraverso stati sicuri), rilevare lo stallo e recupero.
2) Si consideri la seguente procedura in C:

(1.2)

float calcola(float a, float b, float c, float d, float e, float f)


{
float x, y, z, t;
x=a*sqrt(b) ; y=c*c*d; z=y+e ; t=y*f ; z =x*z ; t=x*t*t ;
return(z*t) ;
}

Si supponga di riscrivere la medesima procedura usando un linguaggio di programmazione


concorrente. Precisamente, si riscriva la procedura usando
le primitive (p=fork(processo), join(p))
le primitive (cobegin, coend).
Si considerino i seguenti sei task, espressi in pseudo-codice:
T1(a,b){ x=a*sqrt(b) ; return x ; }
T2(c,d){ y=c*c*d; return y;}
T3(y,f){ t=y*f ; return t ;}
T4(y,e){ z=y+e ; return z ;}
T5(x,t){ t=x*t*t ; return t ;}
T6(x,z){ z=x*z ; return z ;}
Con questa definizione, la funzione espressa al 2o punto, in ambiente concorrente, espressa con il
seguente grafo :
a

T2

T1

T3

T4

T5

T6

z+t

Usando un linguaggio di programmazione concorrente che realizza la concorrenza con le istruzioni


cobegin/coend, il grafo corrisponde al seguente programma concorrente:
float calcola(a,b,c,d,e,f)
{

cobegin
T1;
T2;
coend;
cobegin
T3;
T4;
coend;
cobegin
T5;
T6;
coend;
return(z+t);
}
Usando invece un linguaggio che realizza la concorrenza con le primitive fork/join, si pu
realizzare il seguente programma concorrente:
float calcola(a,b,c,d,e,f)
{
P1=fork(T1);
T2;
P2=fork(T4);
T3;
Join(P1);
T5;
Join(P2);
T6;
Return(z+t):
}
3) Si supponga di essere in un ambiente a memoria condivisa. Scrivere uno pseudocodice del
produttore/consumatore usando lalgoritmo di Peterson per la mutua esclusione e due semafori
per le sincronizzazioni. Quali sono le variabili da condividere?
Il codice, usando semafori e lalgoritmo di Peterson, potrebbe essere scritto in pseudo-C in questo
modo:
produttore(){
while(true){
p=0; t=1;
elto=produci();
down(pieno);
while(q!=1 && t==1) ;
*ptr++=elto ;
p=1 ;
up(vuoto) ;
}
}

consumatore(){
while(true){
q=0; t=2;
down(vuoto);
while(p!=1 && t==2) ;
elto=*ptr-- ;
q=1 ;
up(pieno) ;
}
}

Questo pseudo-codice usa un buffer condiviso (puntato da ptr, puntatore condiviso), due semafori
per la sincronizzazione del buffer, pieno e vuoto, e tre variabili condivise, p, q e t. per
lalgoritmo di Peterson.
4) i supponga che una architettura di un calcolatore offra al programmatore una istruzione atomica
scambia(a,b), che realizza la seguente funzione:
scambia(short &a, short &b){
short t;
t = *a; *a = *b; *b = t;
}

Come si puo utilizzare questa istruzione per proteggere una sezione critica ?

(1.4)

La mutua esclusione si pu ottenere usando la procedura atomica scambia nel seguente modo:
a=0: //variabile globale
processo1{
b=1;
while(true) {
while(b==1) scambia(a,b);
Sezione_critica1();
scambia(a,b);
}
}

5)

processo2{
c=1;
while(true) {
while(c==1) scambia(a,c);
Sezione_critica1();
scambia(a,c);
}
}

Si consideri un programma multithreaded in java costituito dai seguenti due thread,


dove la variabile i condivisa tra i thread stessi:
(1.2)

public class scrivi1 extends Thread{


public void run(){
while(true){
i=1;
System.out.println(Sono il thread
Scrivi1: i=+i);
}
}
}

public class scrivi2 extends Thread{


public void run(){
while(true){
i=2;
System.out.println(Sono il thread
scrivi2: i=+i);
}
}
}

Lo scopo dei due thread e semplicemente quello di scrivere 1 e 2 rispettivamente, ma


ovviamente ci sono degli interleaving per cui si possono avere delle scritture (errate) tipo:
Sono il thread Scrivi1: 2
Sono il thread Scrivi1: 2

Come si pu risolvere questo problema usando i monitor di java? Modificare il programma per
questo scopo.
Un modo per risolvere il problema pu essere di definire una classe condivisa, chiamata diciamo S,
come segue:
public class S{
private int i;
S(){i=0;}

synchronized
i=1;
}
synchronized
i=2;
}

void s1(){
System.out.println(Sono il thread scrivi1: i=+i);
void s2(){
System.out.println(Sono il thread scrivi2: i=+i);

Con questa classe, i thread scrivi1 e scrivi2 diventano:


public class scrivi1 extends Thread{
S a;
Scrivi1(S a){this.a=a;}
public void run(){
while(true){
a.s1();
}
}
}

public class scrivi2() extends Threads{


S b;
Scrivi2(S a){this.b=a;}
public void run(){
while(true){
b.s2();
}
}
}

6) Si supponga di avere un programma Java, costituito da due threads (riportati in seguito) che
vengono attivati in concorrenza dal programma principale. I due thread condividono la variabile
i. Si vuole che il programma produca una sequenza di numeri uguali, in particolare 5 5 5 5
public class Inc extends Thread{
public void run(){
while(true){
System.out.println(i=+i);
i++;
}
}
}

public class Dec extends Thread{


public void run(){
while(true){
System.out.println(i=+i);
i--;
}
}
}

Rispondere alle seguenti due domande:

a) Mostrare un interleaving tale che i due thread non producono la sequenza desiderata..
b) Modificare il programma usando alcuni semafori per avere luscita desiderata ed indicando il
valore iniziale degli stessi.
Inizializzando la variabile i a 5, si pu immaginare ad esempio la seguente schedulazione:
public class Inc()extends Threads{
public void run(){
while(true){
System.out.println(i=+i);
i++;
}
}
}

public class Dec()extends Threads{


public void run(){
while(true){
System.out.println(i=+i);
i--;
}
}
}

cio, il primo thread inizia, stampa 5, poi passa al secondo, che stampa 5 e ritorna al primo, che
incrementa i che passa a 6 e stampa 6, poi passa al secondo che stampa 6 e decrementa etc. La
sequenza stampata pertanto: 556655
Una possibile soluzione , tralasciando le definizioni dei semafori:
public class Inc()extends Threads{
public void run(){
while(true){
mutex.down();
s1.down():
System.out.println(i=+i);
s2.up();
s3.down();

i++;
mutex.up();
}
}
}
public class Dec()extends Threads{
public void run(){

while(true){
s2.down();
System.out.println(i=+i);
i--;
s3.up();
s1.up():
}
}
}

Naturalmente, la variabile i condivisa tra i thread ed inizializzata a 5. I semafori sono


inizializzati cos: mutex=1, s1=1, s2=0, s3=0.
Inoltre il semaforo mutex puo essere anche evitato, se si suppone di avere un solo thread Inc().
7) Scrivere lo psudocodice per risolvere il problema dei lettori/scrittore.
Assumiamo che esista una classe condivisa Ls che contiene la variabile privata nrlett (numero
lettori) con i relativi metodi set_nrlett(), get_nrlett(), dec_nrlett(), inc_nrlett() ed i metodi
leggi_archivio() e scrivi_archivio() con gli ovvi significati.
Tralasciando per semplicit le definizioni inerenti ai semafori, il problema si pu risolvere in Java
nel seguente modo:
public class Lettore extends Thread{
private int i;
private Ls buf;
Lettore(int i, Ls buf){this.i=i; this.buf=buf;}
public void run(){
while(true){
mutex.down();
buf.inc_nrlett();
if(buf.get_nrlett==1) archivio.down();
mutex.up();
buf.leggi_archivio();
mutex.down();
buf.dec_nrlett();
if(buf.get_nrlett==0) archivio.up();
mutex.up();
}
}
}

public class Scrittore extends Thread{


private Ls buf;
Scrittore(Ls buf){this.buf=buf;}
public void run(){
while(true){
Attendi_aggiornamenti();
Archivio.down();
buf.scrivi_archivio();
archivio.up();
}
}

8) Si supponga che un Sistema Operativo con 5 processi concorrenti si trovi nella seguente
situazione, nei confronti delle risorse:
(1)
4 tipi di risorse. Capacit del sistema: |4 4 4 4|
Risorse allocate nel sistema:
1
0
0
1
0

0
1
1
1
0

0
1
1
0
1

1
0
0
0
1

Risorse richieste:

3
4
3
2
2

3
2
2
1
3

2
2
1
1
2

1
3
1
2
2

Il sistema in uno stato sicuro o no? Nel caso che sia in uno stato non sicuro, cosa si puo fare per
risolvere il problema?
Per vedere se uno stato sicuro, possiamo vedere se le richieste possono essere soddisfatte.
Intanto possiamo vedere che le risorse disponibili sono: V=|2 1 1 2|.
Possiamo allora soddisfare le richieste del 4 processo che, quando termina rilascia le sue risorse
e quindi le risorse disponibili sono: V=|3 2 1 2|. Possiamo allora eseguire il 3 processo. Dopo la
terminazione, si arriva ad avere V=!3 3 2 2|. Possiamo allora eseguire il primo processo,
arrivando ad avere alla sua terminazione: V=|4 3 2 3|. Possiamo eseguire il secondo, con V=|4 4 3
3| ed infine il quinto. Esiste una schedulazione che soddisfa tutte le richieste: lo stato sicuro.
9.

Si consideri il seguente pseudocodice in memoria condivisa


(descrive due thread che leggono un archivio; in questo esercizio non ci sono thread che
modificano larchivio). La variabile nrlettori e dunque condivisa.
lettore1(){
while(true){
down(mutex);
nrlettori++;
if(nrlettori==1)
printf(primo
lettore);
up(mutex);
leggi_archivio();
down(mutex);
nrlettori--;
if(nrlettori==0)
printf(ultimo
lettore);
up(mutex);
}
}

lettore2(){
while(true){
down(mutex);
nrlettori++;
if(nrlettori==1)
printf(primo
lettore);
up(mutex);
leggi_archivio();
down(mutex);
nrlettori--;
if(nrlettori==0)
printf(ultimo
lettore);
up(mutex);
}
}

Modificare questo pseudocodice senza usare operazioni atomiche, ma usando una


soluzione ad attesa attiva.
Un modo per usare lattesa attiva con lalternanza stretta e (mutex e una variabile
condivisa):
lettore1(){
while(true){
while(mutex==0);
nrlettori++;
if(nrlettori==1)
printf(primo
lettore);
mutex=0;
leggi_archivio();
while(mutex==0);
nrlettori--;
if(nrlettori==0)

printf(ultimo
lettore);
mutex=0;
}
}
lettore2(){
while(true){
while(mutex==1);
nrlettori++;
if(nrlettori==1)
printf(primo
lettore);

mutex=1;
leggi_archivio();
while(mutex=1);
nrlettori--;
if(nrlettori==0)

10.

printf(ultimo
lettore);
mutex=1;
}
}

Si consideri il seguente pseudocodice:


PC1(){

PC2(){
while(true){
el=rimuovi();
consuma(el);
dato=produci();
inserisci(dato);
}

while(true){
val=rimuovi();
consuma(val);
elto=produci();
inserisci(elto);
}
}

Si tratta di due Thread concorrenti che si passano i dati attraverso una variabile
condivisa.
La procedura rimuovi() preleva il valore della variabile condivisa;
la procedura inserisci(x) scrive il valore x nella variabile condivisa.
I due thread funzionano sia da produttori che da consumatori: il primo preleva il dato
della variabile, lo consuma e scrive un altro dato nella variabile. Il secondo prende il
dato appena scritto, lo consuma e scrive un altro dato nella variabile condivisa.
In sostanza i due thread si passano lun laltro un dato alla volta.
Sincronizzare i due thread usando due semafori (gestiti con le procedure down() e up()),
tenendo conto che la variabile inizialmente contiene un elemento valido e che si desidera
far partire inizialmente il Thread PC1.
Dare i valori iniziali dei semafori.
La sincronizzazione viene effettuata con due semafori (sem1 e sem2, inizializzati cosi:
sem1=1, sem2=0) nel seguente modo:
PC1(){
while(true){
down(sem1);
el=rimuovi();
consuma(el);
dato=produci();
inserisci(dato);
up(sem2);
}
}
PC2(){
while(true){
down(sem2);
val=rimuovi();
consuma(val);
elto=produci();
inserisci(elto);
up(sem1);
}
}

11.

Si scriva il codice di una applicazione multithread in Java che utilizzi strutture semaforiche
realizzate anchesse in Java.

I semafori possono essere implementati con la classe Semaf.java:

import java.io.*;
public class Semaf {
private int s;
Semaf(){ s=1;}
synchronized void down() {
if( s <= 0 ) { try{ wait();} catch(InterruptedException e){}; }
s--;
}
synchronized void up() {
s++;
notify();
}
}

Esempio di Implementazione di Thread che usano la classe Semaf:


import java.util.*;
public class P1 extends Thread{ // primo processo
Semaf a;
P1(Semaf p){ this.a=p; }
public void run(){
while(true) {
a.down();
try{Thread.sleep(0);} catch (InterruptedException e){};
System.out.print("io sono il ");
System.out.print("thread ");
System.out.println("P1 ");
a.up();
}
}
}
import java.util.*;
public class P2 extends Thread{ // secondo processo
Semaf a;
P2(Semaf p){ this.a=p; }
public void run(){
while(true) {
a.down();
try{Thread.sleep(0);} catch (InterruptedException e){}
System.out.print("invece io rappresento ");
System.out.print("l'esecuzione ");
System.out.println("P2 ");
a.up();
}
}
}
import java.util.*;
public class P3 extends Thread{ // terzo processo
Semaf a;
P3(Semaf p){ this.a=p; }
public void run(){
while(true) {
a.down();
try{Thread.sleep(0);} catch (InterruptedException e){}
System.out.print("invece io rappresento ");

System.out.print("l'esecuzione ");
System.out.println("P3 ");
a.up();
}

}
}

public class Avvia{


public static void main(String[] a){
Semaf s=new Semaf();
P1 c=new P1(s);
P2 t=new P2(s);
P3 z=new P3(s);
c.start(); t.start(); z.start();

}
}

Senza i semafori (commentando down e up) si ottiene un funzionamento


del tipo:
...

io sono il thread P1
invece io rappresento l'esecuzione P2
io sono il thread P3
io sono il thread P1
io sono il invece io rappresento thread l'esecuzione P3
P2
io sono il thread P3
...

12. Siano dati i seguenti tre task, rappresentati con dei blocchi di istruzioni in C:
T1:
{if(a==0) y=b; else z=0;}
T2:
{if(a==1) x=a+b; else c=0;}
T3:
{if(a==2) b=x+y; else x=1;}
Si consideri il sistema di tre task: C={ {T1,T2,T3}, 0}, cioe linsieme dei tre task senza
vincoli di precedenza.
Il sistema C, se eseguito in concorrenza, non e determinato.
Per quale coppia ( cioe{T1,T2}, {T1,T3}, {T2,T3}) di task lesecuzione concorrente e
determinata ? dimostrarlo mediante le condizioni di Bernstein.
Le condizioni di Bernstein dicono che se e solo se un insieme di task e mutuamente non
interferente, allora il sistema e determinato.
La condizione di non interferenza tra due task T1 e T2 e la seguente: se D(T) e R(T)
sono il dominio e il condominio del task T, devono essere verificate le tre condizioni:
R(T1) and R(T2) = 0
R(T1) and D(T2) = 0
R(T2) and D(T1) = 0
Nel nostro caso
D(T1) = {a,b}
D(T2) = {a,b}

R(T1) = {y,z}
R(T2) = {x,c}

D(T3) = {a,x,y}

R(T3) = {b,x}

Da cui si vede che tutti e tre i task hanno una intersezione non nulla degli R().
Prendendo le coppie, si vede anche che R(T1)andR(T2)=0, D(T1)andR(T2)=0,
D(T2)andR(T1)=0 quindi sono non interferenti. Le coppie T1,T3 e T2,T3 hanno
intersezioni non nulle.
Quindi lunica coppia di Task che genera una concorrenza determinata e T1,T2.
13. Si consideri un Sistema Operativo dotato di tre risorse, ciascuna con sette istanze. Il vettore
Capacita e dunque:
W=|7, 7, 7|
In un certo istante, la situazione del sistema e la seguente:
le risorse allocate sono descritte dalla matrice P e quelle richieste dalla matrice Q.

P ==

2
2
3
0

3
2
1
0

0
2
1
4

Q ==

0
1
2
5

0
0
0
0

0
1
3
0

Determinare se il sistema e in deadlock o no. In caso di risposta affermativa, come si


puo modificare il vettore Capacita per non avere deadlock? In caso di risposta negativa,
come si puo modificare il vettore Capacita per avere deadlock?
Il vettore delle risorse disponibili e ovviamente V=|0 1 0|.
Quindi inizialmente solo il processo P1 puo avanzare. Quando termina e rilascia le sue
risorse, il vettore V diventa V=|2 4 0| che sono insufficienti per far avanzare i processi
P2-P3-P4 che sono in deadlock. La risorsa che provoca il blocco e la terza, quindi
aggiungendo una istanza della terza risorsa (cioe partendo con W=|7 7 8|) si sarebbe
potuto evitare il deadlock
14. Si assuma che la memoria centrale di un calcolatore sia di 2Mbyte e sia gestita da un
Memory Manager (MM) a partizioni variabili.
Ad un certo istante, le seguenti partizioni sono non utilizzate:
600 Kbyte
150 Kbyte
400 Kbyte
180 Kbyte
556 Kbyte
160 Kbyte
Il MM riceve le seguenti richieste di memoria (in questordine):
220 Kbyte
400 Kbyte
180 Kbyte
410 Kbyte
Determinare la dimensione media delle partizioni rimaste libere dopo lallocazione, la
frammentazione e la capacita di soddisfare le richieste dopo lutilizzo delle seguenti
strategie di allocazione: first-fit, next-fit, best-fit, worst-fit.
Si determini infine la dimensione media delle partizioni rimaste libere dopo
lallocazione, la frammentazione e la capacita di soddisfare le richieste dopo lutilizzo
del metodo dei compagni (buddy system), nellipotesi che la memoria di 2Mbyte sia
completamente libera inizialmente.
La allocazione first-fit provoca la seguente situazione:
600K
alloco 220K restano 380K alloco 180K restano 200K
150K
restano 150K
400K
alloco 400K
restano 0K

180K
556K
160K
Quindi:

alloco 410 K

restano 180K
restano 146K
restano 160K

5 partizioni rimaste libere dopo lallocazione


Memoria totale rimasta libera: 836K f=0.418
Dimensione media partizioni rimaste libere = 167.2 K
Tutte le richieste di allocazione sono soddisfatte

La allocazione next-fit provoca la seguente situazione:


600K
alloco 220K
restano 380K
150K
restano 150K
400K
alloco 400K
restano 0K
180K
alloco 180K
restano 0K
556K
alloco 410 K
restano 146K
160K
restano 160K
Quindi:

4 partizioni rimaste libere dopo lallocazione


Memoria totale rimasta libera: 836K f=0.418
Dimensione media partizioni rimaste libere = 209 K
Tutte le richieste di allocazione sono soddisfatte

La allocazione best-fit provoca la seguente situazione:


600K
alloco 410K
restano 190K
150K
restano 150K
400K
alloco 220K restano 180K alloco 180K restano 0K
180K
restano 180K
556K
alloco 400 K
restano 156K
160K
restano 160K
Quindi:

5 partizioni rimaste libere dopo lallocazione


Memoria totale rimasta libera: 836K f=0.418
Dimensione media partizioni rimaste libere = 167.2 K
Tutte le richieste di allocazione sono soddisfatte

La allocazione worst-fit provoca la seguente situazione:


600K
alloco 220K
restano 380K
150K
restano 150K
400K
alloco 180K
restano 220K
180K
restano 180K
556K
alloco 400 K
restano 156K
160K
restano 160K
Quindi:

6 partizioni rimaste libere dopo lallocazione


Memoria totale rimasta libera: 1246K f=0.623
Dimensione media partizioni rimaste libere = 207.6 K
La richiesta di allocare 410K non puo essere soddisfatte

Con il metodo dei compagni, lallocazione porta al seg. risultato:


220K vengono allocati nella partizione di 256K che parte da 0. Memoria rimasta =36K
400K vengono allocati nella partizione di 512 K che parte da 512k. Memoria rimasta=112K

180K vengono allocati nella partizione di 256 K che parte da 256k. Memoria rimasta=76K
410K vengono allocati nella partizione di 512 K che parte da 1M. Memoria rimasta=102K
Quindi:

partizione non utilizzata: da 512K che parte da 1.5 M


Dimensione media: 512K
La frammentazione (interna) e:
(totale memoria rimasta)/(totale memoria utilizzata)=326K/1536K=0.212

15. Un calcolatore con 16 Mbyte di memoria ha un MM (memory manager) che gestisce la


memoria contigua con partizioni variabili.
Il MM ha due possibili modi per descrivere la situazione in memoria:
a. mediante lista concatenata per descrivere le partizioni libere e occupate: in questo
caso si supponga che vengano impiegati 8 byte per elemento della lista.
b. mediante un Bitmap (1 bit per ogni blocco di 512 byte)
Calcolare quale il livello medio di multiprogrammazione tale che la Bitmap occupi la
stessa dimensione della lista concatenata.
Dimensione della Bitmap:
16Mbyte/512byte = 32768 bit cioe 4096 byte,
Se n e il numero delle partizioni libere e m il numero delle partizioni occupate, allora la
dimensione della lista e:
8 byte * n + 8 byte * m
Dato che, per la regola del 50%, in media e allequilibrio n=m/2, si ha
8 byte * m/2 + 8 byte *m = 12 * m [byte]
Quindi la dimensione della lista e 12 volte il numero delle partizioni occupate.
Assumendo che ciascuna partizione occupata sia occupata da un processo, il numero
delle partizioni occupate, m, e uguale al numero dei processi nel sistema.
Dalluguaglianza tra le dimensioni:
dimensioneBitmap = dimensioneListaconcatenata
si ha
4096 = 12m da cui m=341,3
Quindi ci vogliono 342 processi per avere luguaglianza tra le dimensioni.

Potrebbero piacerti anche