miércoles, 20 de marzo de 2013
Hoy en el parque
Esta mañana estaba durmiendo en mi sofá, en medio de un parque y paralizado.
De pronto un montón de niños de la escuela salían corriendo alrededor para hacer
la práctica del colegio que consistía en arreglar arados hechos de tablones,
engancharlos a los tractores arrancarlos y salir corriendo en ellos a modo de carrera.
El dueño de los tractores me vio ahí durmiendo y al reconocerme como padre que
soy de dos de sus alumnos me ofreció un trozo de carne roja
de buey "¡Para los chicos! ¡que se alimenten!" dijo,
me levanté interesado y apartó un toldo al lado de uno de los tractores
descubriendo un montón enorme de carne para cortarme un filete con mi cuchillo
de cocina. En cuanto descubrió la carne, una de las ovejas que pasaba por allí se
lanzó enseguida hacia ella con ansias por devorarla; cogí mi filete para llevarlo a casa
y la oveja me atacó mordiéndome la mano para arrebatármelo ante el estupor de los
presentes. Ha sido entonces cuando he oído su risa en el descansillo y una llamada a
la puerta de tres golpes secos, he podido abrir los ojos, estaba despierto
pero seguía paralizado y no tenía la menor intención de abrir la puerta para
descubrir que no sólo el recuerdo de su risa se había vuelto agridulce si no que
además había tenido una especie de alucinación auditiva.
lunes, 30 de abril de 2012
Implementación con pthread
Después de comentarlo con algún que otro entendido del irc, me comentaron que lo mejor era encapsular todo lo referente a mutex y condiciones dentro de la propia estructura de datos de la lista:
A su vez, meterlo todo en la cabecera synclist.h y a continuación emplearla desde por ejemplo:
#ifndef SYNCLIST_H #define SYNCLIST_H #include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <string.h> struct synclist { int max_size; int max_string_size; char **element; pthread_mutex_t mutex; pthread_cond_t read_done,write_done; int size; }; void synclist_init(struct synclist *o, int max_size,int max_string_size) { int i; pthread_mutex_init(&o->mutex,NULL); pthread_cond_init(&o->read_done,NULL); pthread_cond_init(&o->write_done,NULL); o->max_size = max_size; o->max_string_size = max_string_size; o->element = (char **)malloc(o->max_size*sizeof(char *)); for(i=0;i<o->max_size;i++) { o->element[i] = (char *)malloc(o->max_string_size*sizeof(char)); } o->size = 0; } void synclist_destroy(struct synclist *o) { int i; pthread_mutex_lock(&o->mutex); pthread_mutex_destroy(&o->mutex); pthread_cond_destroy(&o->read_done); pthread_cond_destroy(&o->write_done); for(i=0;i<o->max_size;i++) { free(o->element[i]); } free(o->element); } void synclist_push(struct synclist *o,char *element) { pthread_mutex_lock(&o->mutex); while ( o->size >= (o->max_size) ) { /* List is full, waiting for read_done */ pthread_cond_wait(&o->read_done,&o->mutex); } /* Write and unlock */ strncpy(o->element[o->size],element,o->max_string_size); o->size++; pthread_cond_signal(&o->write_done); pthread_mutex_unlock(&o->mutex); } void synclist_pop(struct synclist *o,char *element) { pthread_mutex_lock(&o->mutex); while ( o->size <= 0 ) { /* Empty list, waiting for write_done */ pthread_cond_wait(&o->write_done,&o->mutex); } /* Read and unlock */ strncpy(element,o->element[o->size-1],o->max_string_size); o->size--; pthread_cond_signal(&o->read_done); pthread_mutex_unlock(&o->mutex); } #endif
A su vez, meterlo todo en la cabecera synclist.h y a continuación emplearla desde por ejemplo:
#include "synclist.h" #include <stdio.h> #define LIST_SIZE 10 #define BUFFER_SIZE 1024 #define READERS 5 void *writer(void *mysl) { int counter = 0; char msg[BUFFER_SIZE]; struct synclist *sl; sl = (struct synclist *)mysl; while ( 1 ) { sprintf(msg,"message #%d",counter); counter++; synclist_push(sl,msg); printf("Writing msg %s (list size %d) (thread id %d)\n", msg,sl->size,abs((unsigned int)pthread_self())); fflush(stdout); } } void *reader(void *mysl) { char msg[BUFFER_SIZE]; struct synclist *sl; sl = (struct synclist *)mysl; while ( 1 ) { synclist_pop(sl,msg); printf("Reading msg %s (list size %d) (thread id %d)\n", msg,sl->size,abs((unsigned int)pthread_self())); fflush(stdout); } } int main(int argc,char *argv[]) { struct synclist sl; char buffer[BUFFER_SIZE]; pthread_t t[READERS+1]; int i; synclist_init(&sl,LIST_SIZE,BUFFER_SIZE); pthread_create(&t[0],NULL,writer,(void *)&sl); for(i=0;i<READERS;i++) { pthread_create(&t[i+1],NULL,reader,(void *)&sl); } while ( 1 ) { continue; } synclist_destroy(&sl); return 0; }
viernes, 27 de abril de 2012
One producer, two consumers and a fail
Pues bien, aquí comentando una prueba cronometrada que me tocó hacer anteayer; resulta que tenía 4 horas para implementar un programita multihilo en C y es una de las cosas que siempre tenía ilusión por aprender; el tema de garantizar que la gestión de recursos compartidos entre hilos vaya como debe.
Pues bien, googleo un poco, llego a aquí y digo: "bien, el último código es casi igual que lo que me piden añadiendo un consumidor"; es decir: tenía que implementar tres hilos, el primero debía escribir cadenas en una lista, y los dos restantes ir leyendo mensajes de dicha lista purgándola a su vez.
Bien, en cosa de una hora me aclaro de qué significa cada parte, los mutex y los eventos e implemento un maravilloso programa que parece hacer lo que se pide que haga a excepción de que no emplea la lista y si le sacas los sleeps (que me pidieron insertar entre lecturas y escrituras) acaba en un rotundo deadlock a los pocos segundos de ejecución. Viendo que no estaba a la altura de lo que se pedía, envío el fail pensando: "bueno ... ya que he invertido una hora en esta cosa por lo menos lo envío qué carajo !"
Bueno, la cuestión es que ayer noche me piqué para ver cuál era el fallo y acabé con esto:
La primera parte, es la definición de la lista de cadenas que contendrá los mensajes a compartir entre el escritor y los lectores; en concreto un productor (Writer) y tantos lectores como READERS (en el caso 10). Para la sincronización empleo un mutex _hMutex y dos eventos _hWriteDone y _hReadDone.
El productor Writer inicializa el contador de mensajes counter a 0 y se mete en un bucle infinito donde inicialmente espera luz verde para acceder a la lista: WaitForSingleObject(_hMutex,INFINITE), a continuación chequea si la lista está llena en cuyo caso libera _hMutex y espera que se produzca alguna lectura WaitForSingleObject(_hReadDone,INFINITE) y en caso contrario añade un mensaje a la lista, libera _hMutex y señala el evento de que se ha producido una nueva inserción en la lista: PulseEvent(_hWriteDone).
Los consumidores Reader empiezan esperando acceso a la lista esperando _hMutex libre; una vez tienen acceso comprueban si la lista está vacía en cuyo caso esperan a que el escritor escriba algo en ella tras liberar _hMutex de nuevo: WaitForSingleObject(_hWriteDone,INFINITE).
En caso de que la lista contenga algún elemento, proceden a extraerlo y mostrarlo por pantalla; liberan _hMutex y señalan que se ha producido una lectura de modo que Writer sepa que la lista se ha purgado.
Creo que lo he conseguido hacer de forma correcta, lo he probado con diferentes tamaños de lista, retardos en las lecturas (para forzar la situación en que la lista se llena y algunos mensajes no llegan a ser escritos: list overflow, message lost y tal ...) y distintos números de lectores, etc ... y parece que chuta correctamente durante horas :)
100193 written by thread 372 aka W1 (list size: 99992) and read by thread 4852 aka R5
100283 written by thread 372 aka W1 (list size: 99992) and read by thread 4852 aka R5
List overflow, message lost: 100292 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100293 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100294 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100295 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100296 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100297 written by thread 372 aka W1 (list size: 100000)
100291 written by thread 372 aka W1 (list size: 99999) and read by thread 4852 aka R5
100290 written by thread 372 aka W1 (list size: 99998) and read by thread 5652 aka R9
100289 written by thread 372 aka W1 (list size: 99997) and read by thread 1388 aka R10
100288 written by thread 372 aka W1 (list size: 99996) and read by thread 3400 aka R8
100287 written by thread 372 aka W1 (list size: 99995) and read by thread 5064 aka R2
List overflow, message lost: 100303 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100304 written by thread 372 aka W1 (list size: 100000)
En cualquier caso, el ejercicio me ha servido de motivación para meterle mano al tema de comunicación entre hilos que siempre hasta ahora los había soltado en paralelo de forma independiente y/o utilizando frameworks que encapsulaban la complejidad de acceso a recursos compartidos entre ellos; ahora lo siguiente es implementarlo usando pthreads en Linux y ya si eso tratar de añadirle más productores a ver cómo. Si alguien que entiende ve algún fallo o error de concepto y/o forma alternativa de hacer lo mismo más clara y eficiente y además se digna a dejarlo en comentarios le estaré eternamente agradecido.
Este video es una Informacion Real,solamente pretendo Informar de la Noticia
Sucribete y dar a Like. Muchisimas Gracias.
Pues bien, googleo un poco, llego a aquí y digo: "bien, el último código es casi igual que lo que me piden añadiendo un consumidor"; es decir: tenía que implementar tres hilos, el primero debía escribir cadenas en una lista, y los dos restantes ir leyendo mensajes de dicha lista purgándola a su vez.
Bien, en cosa de una hora me aclaro de qué significa cada parte, los mutex y los eventos e implemento un maravilloso programa que parece hacer lo que se pide que haga a excepción de que no emplea la lista y si le sacas los sleeps (que me pidieron insertar entre lecturas y escrituras) acaba en un rotundo deadlock a los pocos segundos de ejecución. Viendo que no estaba a la altura de lo que se pedía, envío el fail pensando: "bueno ... ya que he invertido una hora en esta cosa por lo menos lo envío qué carajo !"
Bueno, la cuestión es que ayer noche me piqué para ver cuál era el fallo y acabé con esto:
#include "StdAfx.h" #include <windows.h> #include <iostream> #include <stdlib.h> #include <math.h> #define MAX_LIST_SIZE 100000 #define MAX_STRING_SIZE 256 #define READERS 10 HANDLE _hMutex, _hWriteDone, _hReadDone; /* List structure, init, pop and push methods */ struct strlst { int size; char list[MAX_LIST_SIZE][MAX_STRING_SIZE]; } _strlst; void pop_list(char *str,struct strlst *sl) { if ( sl->size == 0 ) { fprintf(stderr,"List empty\n"); str[0] = '\0'; return; } strcpy(str,sl->list[sl->size-1]); sl->size--; return; } void push_list(char *str,struct strlst *sl) { if ( sl->size >= (MAX_LIST_SIZE-1) ) { fprintf(stderr,"List overflow, message lost: %s\n",str); return; } strcpy(sl->list[sl->size],str); sl->size++; return; } void init_list(struct strlst *sl) { sl->size = 0; } /* Writer function */ void Writer(void *myname) { char msg[MAX_STRING_SIZE]; char name[MAX_STRING_SIZE]; int counter = 0; strcpy(name,(char *)myname); while ( true ) { while (true) { if (WaitForSingleObject(_hMutex, INFINITE) == WAIT_FAILED) { /* Something went wrong */ ExitThread(0); } if ( _strlst.size > MAX_LIST_SIZE-1 ) { /* Can't write anything, list is full, waiting to a reader to pop an element */ ReleaseMutex(_hMutex); WaitForSingleObject(_hReadDone, INFINITE); continue; } break; } /* Accessing the list and pulsing write event */ sprintf(msg,"%d written by thread %d aka %s (list size: %d)",counter,GetCurrentThreadId(),name,_strlst.size+1); counter++; push_list(msg,&_strlst); ReleaseMutex(_hMutex); PulseEvent(_hWriteDone); } } /* Reader function */ void Reader(void *myname) { char name[MAX_STRING_SIZE]; strcpy(name,(char *)myname); char msg[MAX_STRING_SIZE]; while(true) { if (WaitForSingleObject(_hMutex, INFINITE) == WAIT_FAILED) { /* Something went wrong */ ExitThread(0); } if (_strlst.size == 0) { /* Nothing to read on the list waiting for the writer */ ReleaseMutex(_hMutex); WaitForSingleObject(_hWriteDone, INFINITE); continue; } /* Reading the list and pulsing read event */ pop_list(msg,&_strlst); printf("%s and read by thread %d aka %s\n",msg,GetCurrentThreadId(),name); ReleaseMutex(_hMutex); Sleep(10*rand()/RAND_MAX); PulseEvent(_hReadDone); } } void main() { HANDLE TName[1+READERS]; DWORD ThreadID; char names[1+READERS][MAX_STRING_SIZE]; /* init list */ init_list(&_strlst); _hMutex= CreateMutex(NULL, FALSE, NULL); _hWriteDone= CreateEvent(NULL, TRUE, FALSE, NULL); _hReadDone= CreateEvent(NULL, TRUE, FALSE, NULL); sprintf(names[0],"W1"); /* creating writer thread */ TName[0]= CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Writer, (void *)names[0], 0, &ThreadID); for(int i=0;i<READERS;i++) { /* creating reader i+1 */ sprintf(names[i+1],"R%d",i+1); TName[i+1]= CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Reader, (void *)names[i+1], 0, &ThreadID); } WaitForMultipleObjects(READERS+1, TName, TRUE, INFINITE); CloseHandle(TName); }
La primera parte, es la definición de la lista de cadenas que contendrá los mensajes a compartir entre el escritor y los lectores; en concreto un productor (Writer) y tantos lectores como READERS (en el caso 10). Para la sincronización empleo un mutex _hMutex y dos eventos _hWriteDone y _hReadDone.
El productor Writer inicializa el contador de mensajes counter a 0 y se mete en un bucle infinito donde inicialmente espera luz verde para acceder a la lista: WaitForSingleObject(_hMutex,INFINITE), a continuación chequea si la lista está llena en cuyo caso libera _hMutex y espera que se produzca alguna lectura WaitForSingleObject(_hReadDone,INFINITE) y en caso contrario añade un mensaje a la lista, libera _hMutex y señala el evento de que se ha producido una nueva inserción en la lista: PulseEvent(_hWriteDone).
Los consumidores Reader empiezan esperando acceso a la lista esperando _hMutex libre; una vez tienen acceso comprueban si la lista está vacía en cuyo caso esperan a que el escritor escriba algo en ella tras liberar _hMutex de nuevo: WaitForSingleObject(_hWriteDone,INFINITE).
En caso de que la lista contenga algún elemento, proceden a extraerlo y mostrarlo por pantalla; liberan _hMutex y señalan que se ha producido una lectura de modo que Writer sepa que la lista se ha purgado.
Creo que lo he conseguido hacer de forma correcta, lo he probado con diferentes tamaños de lista, retardos en las lecturas (para forzar la situación en que la lista se llena y algunos mensajes no llegan a ser escritos: list overflow, message lost y tal ...) y distintos números de lectores, etc ... y parece que chuta correctamente durante horas :)
100193 written by thread 372 aka W1 (list size: 99992) and read by thread 4852 aka R5
100283 written by thread 372 aka W1 (list size: 99992) and read by thread 4852 aka R5
List overflow, message lost: 100292 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100293 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100294 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100295 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100296 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100297 written by thread 372 aka W1 (list size: 100000)
100291 written by thread 372 aka W1 (list size: 99999) and read by thread 4852 aka R5
100290 written by thread 372 aka W1 (list size: 99998) and read by thread 5652 aka R9
100289 written by thread 372 aka W1 (list size: 99997) and read by thread 1388 aka R10
100288 written by thread 372 aka W1 (list size: 99996) and read by thread 3400 aka R8
100287 written by thread 372 aka W1 (list size: 99995) and read by thread 5064 aka R2
List overflow, message lost: 100303 written by thread 372 aka W1 (list size: 100000)
List overflow, message lost: 100304 written by thread 372 aka W1 (list size: 100000)
En cualquier caso, el ejercicio me ha servido de motivación para meterle mano al tema de comunicación entre hilos que siempre hasta ahora los había soltado en paralelo de forma independiente y/o utilizando frameworks que encapsulaban la complejidad de acceso a recursos compartidos entre ellos; ahora lo siguiente es implementarlo usando pthreads en Linux y ya si eso tratar de añadirle más productores a ver cómo. Si alguien que entiende ve algún fallo o error de concepto y/o forma alternativa de hacer lo mismo más clara y eficiente y además se digna a dejarlo en comentarios le estaré eternamente agradecido.
Este video es una Informacion Real,solamente pretendo Informar de la Noticia
Sucribete y dar a Like. Muchisimas Gracias.
Etiquetas:
fail,
multihilo,
multiple consumers,
multithreading,
producer-consumer,
programación,
win32,
windows
viernes, 25 de junio de 2010
La insoportable levedad del método ausente
Una de las cosas que más me gusta hacer cuando programo en lenguajes interpretados es el hecho de generar métodos nuevos al vuelo; en concreto y gracias al missing_method de Ruby o al AUTOLOAD de Perl, es posible construir de forma dinámica los métodos que necesitamos a medida en que los invocamos sin tener que preocuparnos de su implementación explícita.
Un ejemplo ilustrativo podría basarse en una interfaz con una BBDD SQL, dónde a partir de un convenio de nomenclatura de métodos ... tabla_campo_where_campo(condiciones) obtenemos y ejecutamos la consulta deseada.
De ese modo, a medida en que vamos ejecutando consultas se van generando los nuevos métodos de forma automática, tras una primera llamada, el método deja de ser missing_method y pasa a formar parte de la instancia de la interfaz. El código de la interfaz a continuación:
Un ejemplo ilustrativo podría basarse en una interfaz con una BBDD SQL, dónde a partir de un convenio de nomenclatura de métodos ... tabla_campo_where_campo(condiciones) obtenemos y ejecutamos la consulta deseada.
De ese modo, a medida en que vamos ejecutando consultas se van generando los nuevos métodos de forma automática, tras una primera llamada, el método deja de ser missing_method y pasa a formar parte de la instancia de la interfaz. El código de la interfaz a continuación:
La gracia reside en el hecho de que si utilizamos el método ausente missing_method atrápalo todo de Ruby ... de este modo:

Obtenemos al siguiente respuesta:

Es decir, en la primera llamada parsea el nombre del método invocado inexistente user_passwords_where_id ... a continuación empieza a construir la consulta SQL, finaliza generando el método que falta ... precisamente el invocado haciendo eval method_d pues method_d ya contiene toda la magia negra del truco por muy feo que parezca y a partir de ahí deja de ser un missing_method como se puede comprobar en la siguiente llamada ... :)
Dedicado a Paul Graham del que no entendí nada de lo que decía cuando leía sus libros.
viernes, 2 de octubre de 2009
Terapia de snoozing
... no he tenido más remedio que levantarme a las 7:45 preguntándome sin cesar que efecto o predisposición psicológica puede generar en un individuo la interrupción del sueño siguiendo un compás rítmico de intervalos regulares justo antes de despertar ...
Hoy por la mañana he tenido un ataque de síndrome de Stendhal matemático ... y no, no he visto ninguna maldita bolsa de plástico arrastrada por el viento ... no se trata de eso ...
Ayer le pedí a mi móvil que me despertase a las 6:30 AM pero como tenía que hacer cosas importantes y para no quedarme dormido decidí
programar algunos despertadores más (la memoria puede albergar hasta un total de 10 alarmas) en concreto un par más a las 6:35 y a las 6:40.
El período de snooze por defecto es de 9 minutos y yo lo uso a menudo; de modo que esta mañana al sonar la alarma a las 6:30, le he dado al snooze sin ser demasiado consciente de que estaba desencadenando el ataque de Stendhal que os comento si eso ...
... siguiente bip-bip-bip a las 6:35, un ratito más de sueño ... pero en ésta ocasión sólo hasta las 6:39 (4 minutos de tregua) para culminar a las 6:40 cuando otra vez tengo que darle al botoncito.
La serie resultante de snooze-timings es la siguiente:
5, 4, 1, 4, 4, 1, 4, 4, 1, 4, 4, 1, 4, 4, 1, ....
... no hace falta decir que la he seguido maravillado entre ensoñaciones hasta que su delicioso régimen periódico se ha manifestado con tal fuerza - sí, a esas horas soy aún más corto - que hasta mi adormilada mente no ha tenido más remedio que depertar a las 7:45 preguntándome sin cesar que efecto o predisposición psicológica puede generar en un individuo la interrupción del sueño siguiendo un compás rítmico de intervalos regulares justo antes del amanecer del mismo modo que el beat de una canción nos anima a mover los pies para acabar bailando cuando la escuchamos.
Sigo pensando en ello cuando empiezo a escribir snooze.pl ...
... espero poder reconfigurar el móvil para programar distintos tiempos de snoozing y de ese modo mañana ajustar la primera alarma a las 6:32 con un tiempo de 13 minutos entre alarmas, la segunda a las 6:33 con un snooze a 4 minutos y la última a las 6:35 con periodicidad de 7 minutos generando la serie de intervalos de sueño:
1, 2, 2, 4, 1, 3, 4, 4, 3, 1, 1, 3, 2, 2, 4, 1, 1, 2, 4, 4, 3, 1, 4, ...
lo que no dejo de preguntarme es la hora de levantarme además del humor que tendré si decido no hacerlo hasta que de nuevo perciba la periodicidad de la secuencia de snoozes.
Hoy por la mañana he tenido un ataque de síndrome de Stendhal matemático ... y no, no he visto ninguna maldita bolsa de plástico arrastrada por el viento ... no se trata de eso ...
Ayer le pedí a mi móvil que me despertase a las 6:30 AM pero como tenía que hacer cosas importantes y para no quedarme dormido decidí
programar algunos despertadores más (la memoria puede albergar hasta un total de 10 alarmas) en concreto un par más a las 6:35 y a las 6:40.
El período de snooze por defecto es de 9 minutos y yo lo uso a menudo; de modo que esta mañana al sonar la alarma a las 6:30, le he dado al snooze sin ser demasiado consciente de que estaba desencadenando el ataque de Stendhal que os comento si eso ...
... siguiente bip-bip-bip a las 6:35, un ratito más de sueño ... pero en ésta ocasión sólo hasta las 6:39 (4 minutos de tregua) para culminar a las 6:40 cuando otra vez tengo que darle al botoncito.
La serie resultante de snooze-timings es la siguiente:
5, 4, 1, 4, 4, 1, 4, 4, 1, 4, 4, 1, 4, 4, 1, ....
... no hace falta decir que la he seguido maravillado entre ensoñaciones hasta que su delicioso régimen periódico se ha manifestado con tal fuerza - sí, a esas horas soy aún más corto - que hasta mi adormilada mente no ha tenido más remedio que depertar a las 7:45 preguntándome sin cesar que efecto o predisposición psicológica puede generar en un individuo la interrupción del sueño siguiendo un compás rítmico de intervalos regulares justo antes del amanecer del mismo modo que el beat de una canción nos anima a mover los pies para acabar bailando cuando la escuchamos.
Sigo pensando en ello cuando empiezo a escribir snooze.pl ...
my $alarms = {al1 => { start => 2, period => 13},
al2 => { start => 3, period => 4},
al2 => { start => 5, period => 7},};
my $buzzs = {};
my $end_time = 300;
foreach my $time ( 0..$end_time ) {
foreach my $key ( keys %{$alarms} ) {
next if ( $time < $alarms->{$key}{start} );
( ! ( ($time - $alarms->{$key}{start}) % $alarms->{$key}{period} ) ) ?
$buzzs->{$time} = 1 : ();
}
}
map { print "$_\n"; } sort { $a <=> $b } keys %{$buzzs};
... espero poder reconfigurar el móvil para programar distintos tiempos de snoozing y de ese modo mañana ajustar la primera alarma a las 6:32 con un tiempo de 13 minutos entre alarmas, la segunda a las 6:33 con un snooze a 4 minutos y la última a las 6:35 con periodicidad de 7 minutos generando la serie de intervalos de sueño:
1, 2, 2, 4, 1, 3, 4, 4, 3, 1, 1, 3, 2, 2, 4, 1, 1, 2, 4, 4, 3, 1, 4, ...
lo que no dejo de preguntarme es la hora de levantarme además del humor que tendré si decido no hacerlo hasta que de nuevo perciba la periodicidad de la secuencia de snoozes.
Suscribirse a:
Entradas (Atom)
