{"id":3862,"date":"2019-12-06T15:06:29","date_gmt":"2019-12-06T15:06:29","guid":{"rendered":"http:\/\/www.colorwave.it\/blog\/?p=3862"},"modified":"2019-12-15T11:31:16","modified_gmt":"2019-12-15T11:31:16","slug":"alcune-macro-per-la-definizione-semplificata-dei-thread","status":"publish","type":"post","link":"https:\/\/www.colorwave.it\/blog\/framework\/alcune-macro-per-la-definizione-semplificata-dei-thread\/admin\/2019\/12\/","title":{"rendered":"Alcune macro per la definizione semplificata dei thread"},"content":{"rendered":"<p>In un articolo precedente sono state descritte <a href=\"https:\/\/www.colorwave.it\/blog\/framework\/creazione-di-classi-thread-in-colibri\/admin\/2019\/11\/\">alcune classi per la definizione dei thread,\u00a0 <\/a>semaforizzazione e gestione degli eventi.\u00a0 Le classi\u00a0 utilizzano le API di Windows per la gestione dei thread e la classe CEvent per la sincronizzazione degli stessi in applicazioni multithread. Dalla versione 11, C++ supporta la gestione multithread nelle librerie standard,\u00a0 includendo classi per la loro manipolazione, sincronizzazione e operazioni atomiche a basso livello.\u00a0 Dalla versione 11 in poi C++\u00a0 supporta una gestione della concorrenza indipendente dal sistema operativo, permettendo la costruzione di codice perfettamente portabile.<\/p>\n<p>Le API di Colibri supportano, attraverso le macro <em>CLB_lang<\/em> le nuove funzionalit\u00e0 di gestione della concorrenza di C++. Esponiamo di seguito\u00a0 alcune di queste funzionalit\u00e0, utilizzzando esempi tratti dal codice dell&#8217;applicazione.<\/p>\n<p>Tutte le macro ed i comandi di gestione dei thread sono definiti nel namespace CLB::Threads, contenute nel file di inclusione &#8220;<span style=\"color: #008000;\"><em>CLB_lang_thread_C11.h<\/em><\/span>&#8220;.<\/p>\n<!--nextpage-->\n<h4>$_thread(func_name, p1,..,pn)<\/h4>\n<p>La macro \u00e8 equivalente alla classe\u00a0 <em><span style=\"color: #008000;\">std::thread\u00a0<\/span><\/em>e corrisponde al lancio di una funzione thread, <span style=\"color: #008000;\"><em>func_name<\/em><\/span>, al quale vengono passati i parametri\u00a0 <em>p1,p2.. pn: di seguito \u00e8 riportato un esempio:<\/em><\/p>\n<pre class=\"lang:default decode:true\">bool CLB_disk_mapper::Put_seq_lines(void *buf, int nlines)\r\n{\r\n\t\t__getstr\r\n\t\t$_lock(_$str-&gt;mut)\r\n\r\n\t\tsize_t bsz = nlines * _$str-&gt;lsize;\r\n\t\tif (!_$str-&gt;tbuf)\r\n\t\t{\r\n\t\t\t_$str-&gt;tbuf = (UBYTE *)malloc(bsz);\r\n\t\t\t_$str-&gt;tbuf_size = bsz;\r\n\t\t}\r\n\t\telse {\r\n\t\t\tif (_$str-&gt;tbuf_size &lt; bsz)\r\n\t\t\t{\r\n\t\t\t\t_$str-&gt;tbuf = (UBYTE *)realloc(_$str-&gt;tbuf,bsz);\r\n\t\t\t\t_$str-&gt;tbuf_size = bsz;\r\n\t\t\t\t_$str-&gt;tbuf_bytes = bsz;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (!_$str-&gt;tbuf) return false;\r\n\t\t_$str-&gt;tbuf_bytes = bsz;\r\n\t\tmemcpy(_$str-&gt;tbuf, buf, bsz);\r\n\r\n\t\t$_thread t(_thread_transfer_to_disk, _$str,nlines);\r\n\t\tt.detach();\r\n\r\n\t\treturn true; \/\/ return (_$str-&gt;file.Write(buf, nlines*_$str-&gt;lsize) != -1);\r\n}\r\n<\/pre>\n<p><em><span style=\"color: #008000;\">CLB_disk_mapper<\/span><\/em> \u00e8 una classe\u00a0 che mappa un&#8217;immagine su disco, in un file temporaneo, permettendo l&#8217;accesso trasparente a parti in memoria ed altre residenti su file. Il programmatore non si preoccupa di sapere quale parte \u00e8 su disco, poich\u00e9 l&#8217;immagine \u00e8 comunque completamente accessibile attraverso funzioni di estrazione\/scrittura di pixel o blocchi di linee.<\/p>\n<p>la funzione <em><span style=\"color: #008000;\">CLB_disk_mapper::Put_seq_lines(void *buf, int nlines) <\/span><\/em><span style=\"color: #008000;\">v<\/span>iene richiamata per trasferire su disco un blocco di righe, utilizzando un thread, ed occorrer\u00e0 fare in modo che la funzione, chiamata pi\u00f9 volte, non generi errori di scrittura, non avendo terminato uno dei thread. All&#8217;ingresso della funzione \u00e8 usata la macro<\/p>\n<pre class=\"lang:default decode:true\">\t$_lock(_$str-&gt;mut)\r\n<\/pre>\n<p>la quale rimane in attesa che\u00a0 il mutex\u00a0 _$str-&gt;mut venga sbloccato. Quando si verifica questa condizione, prosegue eseguendo alcune istruzioni preparatorie e lanciando una funzione come thread<\/p>\n<pre class=\"lang:default decode:true\">\t$_thread t(_thread_transfer_to_disk, _$str,nlines);\r\n\tt.detach();\r\n<\/pre>\n<p>La prima riga dichiara un thread t che esegue la funzione\u00a0 <span style=\"color: #008000;\"><em>_thread_transfer_to_disk<\/em><\/span>, passandogli i due parametri richiesti; quindi rilascia il thread t (che quindi continuer\u00e0 l&#8217;esecuzione in modo autonomo) ritornando true;<\/p>\n<p>Chiamando pi\u00f9 volte la funzione Put_seq_lines, questa rimarr\u00e0 in attesa che il mutex <em><span style=\"color: #008000;\">_$str-&gt;mut<\/span> <\/em>venga sbloccato, prima di generare il thread di scrittura su disco.<\/p>\n<p>Analizziamo ora la scrittura della funzione lanciata come thread:<\/p>\n<pre class=\"lang:default decode:true\">void _thread_transfer_to_disk(CLASS_STR, int nlines)\r\n{\r\n\t$_lock(_$str-&gt;mut)\r\n\tUBYTE *buf = _$str-&gt;tbuf;\r\n\tfor (int i=0;i&lt;nlines;i++,buf+=_$str-&gt;lsize)\r\n\t  _$str-&gt;file.Write(buf, _$str-&gt;lsize);\r\n}\r\n<\/pre>\n<p>Osserviamo che la prima istruzione \u00e8 ancora una volta $_lock(_$str-&gt;mut), che ha esattamente la stessa funzione della chiamata in <em><span style=\"color: #008000;\">CLB_disk_mapper::Put_seq_lines.<\/span><\/em><\/p>\n<p>Superata la barriera di blocco, viene eseguito un ciclo di trasferimento delle informazioni su disco, quindi termina il thread;<\/p>\n<p>Per chiarire meglio l&#8217;uso della macro $_lock, dobbiamo fare alcune precisazioni osservando la sua definizione:<\/p>\n<pre class=\"lang:default decode:true\">#define  $_lock(_mutex) std::lock_guard&lt;std::mutex&gt; lk (_mutex);\r\n<\/pre>\n<p>la macro crea una classe <strong><em><span style=\"color: #008000;\">lock_guard<\/span> <\/em><\/strong>alla quale viene assegnato il mutex;<\/p>\n<p>La classe <em><span style=\"color: #008000;\">lock_guard<\/span> <\/em>\u00e8 un wrapper che fornisce un comodo meccanismo in stile <a href=\"https:\/\/www.tomdalling.com\/blog\/software-design\/resource-acquisition-is-initialisation-raii-explained\/\">RAII<\/a> per gestire un mutex in un blocco di codice (nel nostro caso tutta la funzione).<\/p>\n<p><em>La classe lock_guard non \u00e8 copiabile.<\/em><\/p>\n<p>Quando viene creato un oggetto <span style=\"color: #008000;\"><em>lock_guard<\/em><\/span>, questi tenta di diventare proprietario del mutex assegnatogli,\u00a0 attendendo che lo stesso venga sbloccato da chi ne ha il controllo; quindi, a sua volta blocca il mutex, fino al rilascio dell&#8217;istanza\u00a0 <em><span style=\"color: #008000;\">lock_guard<\/span><\/em> all&#8217;uscita del blocco di definizione (la funzione stessa od un blocco di codice {&#8230;} nel quale \u00e8 dichiarato).<\/p>\n<!--nextpage-->\n<h4>$_lock<\/h4>\n<p>la macro\u00a0 <em><span style=\"color: #008000;\">$_lock<\/span><\/em> funziona quindi come una barriera che non viene superata fino a quando il mutex \u00e8 in stato <em>lock; <\/em>appena sbloccato dall&#8217;utente che ne aveva il possesso, il mutex verr\u00e0 bloccato nuovamente verso gli altri utenti, fino a termine esecuzione del blocco di codice.<\/p>\n<h4><strong>$_wait_unlock(mtx)<\/strong><\/h4>\n<p>Questa macro definisce una barriera all&#8217;esecuzione del codice, fino a quando il mutex non viene sbloccato da chi ne ha preso il possesso; <em>Contrariamente alla $<span style=\"color: #008000;\">_lock<\/span> essa non comporta il blocco del mutex nel codice che la usa<\/em>, poich\u00e8, come si pu\u00f2 vedere dalla definizione, la <em><span style=\"color: #008000;\">lock_guard<\/span> <\/em>assegnata \u00e8 definita all&#8217;interno di un blocco di codice <span style=\"color: #008000;\"><em>{..}<\/em><\/span> che termina l&#8217;esecuzione con lo sblocco stesso del mutex:<\/p>\n<pre class=\"lang:default decode:true\">#define $_wait_unlock(_mutex) {$_lock(_mutex)}\r\n<\/pre>\n<p>In altri termini: $<em>_wait_unlock\u00a0<\/em>attende che il mutex venga sbloccato, e prosegue con l&#8217;esecuzione del codice senza bloccarlo nuovamente.<\/p>\n<h4>$_wait_mutex(mtx)<\/h4>\n<p>La macro \u00e8 simile alla <em><span style=\"color: #008000;\">__wait_unlock,\u00a0 <\/span><\/em>ma l&#8217;attesa dello sblocco del mutex \u00e8 effettuata da una specifica funzione thread, <em><span style=\"color: #008000;\">$_<\/span><span style=\"color: #008000;\"><em>wait_<\/em>mutex, <\/span><\/em><span style=\"color: #008000;\"><span style=\"color: #808080;\">non in un blocco di codice:<\/span><\/span><\/p>\n<pre class=\"lang:default decode:true \">#define $_wait_mutex(mtx)  $_thread t(wait_mutex, &amp;(mtx)); t.join(); \r\n<\/pre>\n<p>Come si osserva, la macro definisce un thread t, definito $_w<em><span style=\"color: #008000;\">ait_mutex<\/span><\/em>, gli passa un mutex, quindi (t.join()) attende la terminazione del thread; La funzione <span style=\"color: #008000;\"><em>$_<span style=\"color: #008000;\">w<\/span>ait_mutex <\/em><\/span>\u00e8 la seguente:<\/p>\n<pre class=\"lang:default decode:true\">static void $:wait_mutex(_mutex_ptr mtx) { $_lock(*mtx); }<\/pre>\n<h4>$_get_max_threads(_nth)<\/h4>\n<p>La macro dichiara la costante _nth inizializzandola con il numero di thread disponibili per l&#8217;hardware in uso:<\/p>\n<pre class=\"lang:default decode:true\">  #define $_get_max_threads(_nth)  unsigned long const _nth= std::thread::hardware_concurrency();\r\n<\/pre>\n<p>Per farlo la macro richiama la funzione std::thread::hardware_concurrency() che fornisce l&#8217;informazione.<\/p>\n<p><!--nextpage-->Spesso capita di dovere bloccare l&#8217;utilizzo di una istanza di una classe fino al termine di altre operazioni. Nelle dll di Colibr\u00ec si fa uso della macro $_lock_class(..), applicabile a qualunque classe contenete la dichiarazione <em>__lockable.<\/em><\/p>\n<p>Per capire come funziona, analizziamo la dichiarazione della classe DOCUMENT di Colibri<\/p>\n<pre class=\"lang:default decode:true \" data-url=\"__lockablez\">class AFX_EXT_CLASS DOCUMENT : public DB_object, public FolderOrganizer, public HISTORY\r\n{\r\n      __lockable \r\n\r\npublic:\r\n       DOCUMENT(void);\r\n       virtual ~DOCUMENT(void);\r\n\r\n       \/\/ other funcions and methods..\r\n       ..\r\n       ..\r\n};\r\n<\/pre>\n<p><em><strong><span style=\"color: #008000;\">__lockable<\/span> <\/strong><\/em>\u00e8 una macro che definisce le istanze di DOCUMENT e delle classi derivate come &#8216;<em>bloccabili<\/em>&#8216;, inserita nella dichiarazione della classe (in genere all&#8217;inizio o alla fine). Banalmente \u00e8 una semplice dichiarazione di un mutex (locked), accessibile pubblicamente:<\/p>\n<pre class=\"lang:default decode:true \">  #define __lockable public: MUTEX locked;\t\r\n<\/pre>\n<p>il mutex viene testato attraverso la macro $_lock_class, che costituisce una barriera all&#8217;esecuzione, esattamente come $_lock,\u00a0 utilizzando quest&#8217;ultima al suo interno:<\/p>\n<pre class=\"lang:default decode:true \">#define $_lock_class(cls)  $_lock(cls-&gt;locked)\r\n<\/pre>\n<p>Per mostrarne l&#8217;uso, analizziamo, come al solito, un pezzo di codice di Colibr\u00ec:<\/p>\n<p>Dalla versione 8 Colibr\u00ec ha una nuova DLL, (<em>DLL_vision) contenente <\/em>funzioni di gestione delle telecamere e di manipolazione delle immagini; La DLL fa uso delle funzionalit\u00e0 di <em><span style=\"color: #008000;\">OpenCV<\/span><\/em>.\u00a0 In particolare \u00e8 definita la classe <em><span style=\"color: #008000;\">VideoCamera\u00a0<\/span><\/em>che permette l&#8217;acquisizione di video e immagini:<\/p>\n<pre class=\"lang:default decode:true\">namespace VISION\r\n{\r\n\tclass AFX_EXT_CLASS VideoCamera\r\n\t{\r\n\tpublic:\r\n\t\tVideoCamera();\r\n\t\t~VideoCamera();\r\n\r\n\t\tbool Open(int id = 0);\r\n\t\tbool VideoCamera::Open(int id, float w, float h, float fps);\r\n\r\n\t\tbool Capture();\r\n\r\n\t\t\/** \\brief cattura un frame e lo trasferisce sulla bitmap\r\n\t\t\\param map dove trasferire (deve essere stata inizializzata alle dimensioni del frame)\r\n\t\t*\/\r\n\t\tbool Capture(IMAGE *map);\r\n\r\n\t\t......  \/\/ other functions &amp; info\r\n                .......\r\n\r\n\t\t__use_private_struct\r\n\t};\r\n\r\n\r\n}<\/pre>\n<p>Siamo interessati alla funzione <span style=\"color: #008000;\"><em>Capture(IMAGE *map)\u00a0<\/em><\/span>che cattura un frame dalla telecamera \u00e8 lo trasferisce nella classe IMAGE delle librerie Colibri;\u00a0 IMAGE deriva da DOCUMENT, quindi contiene la dichiarazione <em><span style=\"color: #008000;\">__lockabke.<\/span><\/em><\/p>\n<p>Analizziamo ora la funzione\u00a0<em><span style=\"color: #008000;\">VideoCamera::Capture, <\/span><\/em><span style=\"color: #000000;\">richiamata da un thread di estrazione del video<\/span><\/p>\n<pre class=\"lang:default decode:true\">bool VideoCamera::Capture(IMAGE *map)\r\n{\r\n  __getstr\r\n  $_lock_class(map)\r\n  if (Capture())\r\n     return Get_frame_to(map);      \/\/ convert from Mat image class to IMAGE\r\n   return false;\r\n}<\/pre>\n<p><span style=\"color: #000000;\">La funzione usa la macro<em><span style=\"color: #008000;\"> _lock_class(map)<\/span><\/em> per bloccare il mutex dichiarato con <span style=\"color: #008000;\">__lockabke<\/span> nella IMAGE (che deriva da DOCUMENT..); quindi viene importato in map il frame catturato; l&#8217;istanza <span style=\"color: #008000;\"><em>map<\/em> <\/span>viene cos\u00ec protetta dall&#8217;uso esterno al thead da parte di altre funzioni, fino alla sua terminazione regolare. Inoltre, l&#8217;estrazione del frame non avr\u00e0 luogo fino a quando la classe risulter\u00e0 bloccata da funzioni esterne: nel caso specifico analizziamo il thread chiamante <em><span style=\"color: #008000;\">VideoCamera::Capture<\/span><\/em>:<\/span><\/p>\n<pre class=\"lang:default decode:true\">static void thread_acquire_camera(CLASS_STR)\r\n{\r\n  $_lock(_$str-&gt;acquiring)\r\n  int k = 0;\r\n  while (true)\r\n  {\r\n    if (_$str-&gt;acq_status)\r\n    {\r\n\tif (_$str-&gt;camera-&gt;Capture(igrp-&gt;img[k]))\r\n\t{\r\n          igrp-&gt;w_preview-&gt;Set_doc_image(igrp-&gt;img[k], false);\r\n\t  PAINT(igrp-&gt;w_preview);\r\n\t}\r\n    }\r\n    else break;\r\n    k = (k + 1) % N_IMG_BUF;\r\n  }\r\n}\r\n\r\n<\/pre>\n<p>Dopo avere chiamato la Capture, il thread esegue due operazioni : <em><span style=\"color: #008000;\">igrp-&gt;w_preview-&gt;Set_doc_image(igrp-&gt;img[k], false),<\/span><\/em> che assegna l&#8217;immagine ad un controllo di visualizzazione (WIDGET_IMAGE),\u00a0 quindi usa la macro PAINT per richiedere la visualizzazione del frame.<\/p>\n<p>mostriamo ora la parte di codice che effettuer\u00e0 la visualizzazione:<\/p>\n<pre class=\"lang:default decode:true \">....\r\n....\r\n\tif (_$str-&gt;clb_image) {\r\n \t\t$_lock_class(_$str-&gt;clb_image)\r\n\t\t_$str-&gt;g_image = __$btm(_$str-&gt;clb_image);\r\n\t\tDRW_MEM_IMAGE(_$str, paint);\r\n\t\t_$str-&gt;g_image = 0;\r\n\t\treturn;\r\n\t}\r\n....\r\n....<\/pre>\n<p>_$str-&gt;clb_image \u00e8 lo stesso<em><span style=\"color: #008000;\"> IMAGE*<\/span><\/em> igrp-&gt;img[k] contenuto nel thread sopra riportato, che era stato passato alla <em><span style=\"color: #008000;\">VideoCamera::Capture.\u00a0 <\/span><span style=\"color: #008000;\">$_lock_class(_$str-&gt;clb_image)<\/span> <\/em>costituisce una barriera all&#8217;uso della istanza, superata\u00a0 solo quando le altre utenze (<em><span style=\"color: #008000;\">VideoCamera::Capture..)\u00a0<\/span><\/em>rilasceranno il mutex\u00a0 relativo (ricordati di <em><span style=\"color: #008000;\">__lockabe<\/span><\/em>..).<\/p>\n<hr \/>\n<p><img decoding=\"async\" data-attachment-id=\"3285\" data-permalink=\"https:\/\/www.colorwave.it\/blog\/?attachment_id=3285\" data-orig-file=\"https:\/\/i0.wp.com\/www.colorwave.it\/blog\/wp-content\/uploads\/2018\/03\/word-image-97.png?fit=24%2C24&amp;ssl=1\" data-orig-size=\"24,24\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"word-image\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/www.colorwave.it\/blog\/wp-content\/uploads\/2018\/03\/word-image-97.png?fit=24%2C24&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/www.colorwave.it\/blog\/wp-content\/uploads\/2018\/03\/word-image-97.png?fit=24%2C24&amp;ssl=1\" tabindex=\"0\" role=\"button\" class=\"alignleft size-full wp-image-3285\" src=\"https:\/\/i0.wp.com\/www.colorwave.it\/blog\/wp-content\/uploads\/2018\/03\/word-image-97.png?resize=24%2C24&#038;ssl=1\" alt=\"\" width=\"24\" height=\"24\" data-recalc-dims=\"1\" \/> <span style=\"color: #0000ff;\"><em><span style=\"color: #000080;\"><strong>Per concludere<\/strong>: l&#8217;uso della barriera definita da <span style=\"color: #008000;\">_lock_class<\/span> garantisce che non sia possibile effettuare operazioni di cattura del frame su un&#8217;istanza che \u00e8 in visualizzazione e, viceversa, non sar\u00e0 possibile visualizzare un&#8217;istanza che sta catturando un frame.<\/span>.<\/em><\/span><\/p>\n<hr \/>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In un articolo precedente sono state descritte alcune classi per la definizione dei thread,\u00a0 semaforizzazione e gestione degli eventi.\u00a0 Le classi\u00a0 utilizzano le API di Windows per la gestione dei thread e la classe CEvent per la sincronizzazione degli stessi in applicazioni&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"aside","meta":{"_acf_changed":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"footnotes":"","_jetpack_memberships_contains_paid_content":false,"jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[114,19,120],"tags":[],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p9rwsf-10i","jetpack_likes_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/posts\/3862"}],"collection":[{"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/comments?post=3862"}],"version-history":[{"count":12,"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/posts\/3862\/revisions"}],"predecessor-version":[{"id":3875,"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/posts\/3862\/revisions\/3875"}],"wp:attachment":[{"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/media?parent=3862"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/categories?post=3862"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.colorwave.it\/blog\/wp-json\/wp\/v2\/tags?post=3862"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}