C语言之pthread_cond_t信号变化探究总结(八十)

news2024/9/28 3:05:10

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!

优质专栏:Audio工程师进阶系列原创干货持续更新中……】🚀

人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

更多原创,欢迎关注:Android系统攻城狮

欢迎关注Android系统攻城狮

1.前言

本篇目的: 理解pthread_cond_wait与pthread_cond_timedwait用法区别

函数区别
pthread_cond_wait与pthread_cond_signal函数成对出现,如果没有pthread_cond_signal给它发信号,它死等,等到天荒地老…
pthread_cond_timedwait自己设置超时时间,一旦超过设定时间,自动执行pthread_cond_timedwait函数后边的代码。如果pthread_cond_signal在pthread_cond_timedwait 设置超时时间之前给它发信号,它会收到信号,提前结束等待。
pthread_cond_signal一次只能唤醒一个等待中的线程
pthread_cond_broadcast可以唤醒全部等待中的线程
pthread_mutex_t Linux中用于互斥锁的结构体类型。互斥锁用于保护临界区,确保同时只有一个线程可以访问共享资源。
pthread_cond_t在Linux中,pthread_cond_t是用于线程间同步的条件变量。条件变量用于在线程之间传递信号,帮助线程进行等待和唤醒操作。条件变量通常与互斥锁(pthread_mutex_t)一起使用。当某个线程需要等待某个条件满足时,它会调用pthread_cond_wait函数将自己阻塞,并释放占有的互斥锁。当另一个线程满足了条件并调用了pthread_cond_signal或pthread_cond_broadcast函数发送信号时,等待的线程会被唤醒并重新获取互斥锁,从而继续执行。

2.代码示例

v1.0 pthread_cond_signal和pthread_cond_wait用法

#include <iostream>
#include <thread>
#include <unistd.h>
#include <pthread.h>

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int main() {
  std::thread thread = std::thread([&]() {
    cout << "线程等待信号到来..." << endl;
    pthread_mutex_lock(&mutex);
    //1.如果不调用pthread_cond_signal(&cond)函数发信号,永远死等.
    pthread_cond_wait(&cond, &mutex);
    cout << "Thread timed wait for 10s." << endl;
    pthread_mutex_unlock(&mutex);
  });

  //1.给pthread_cond_wait和pthread_cond_timedwait发信号(它俩是成对出现的),使其后边的代码可以执行.
  sleep(1);
  pthread_cond_signal(&cond);//仅可以唤醒一个线程
  sleep(2);
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
  thread.join();
  return 0;
}

v2.0 pthread_cond_signal和pthread_cond_timedwait用法

#include <iostream>
#include <thread>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int main() {
  //1.给pthread_cond_wait和pthread_cond_timedwait发信号(它俩是成对出现的),使其后边的代码可以执行.
    std::thread thread = std::thread([&]() {
    cout << "线程等待信号到来..." << endl;
    
    struct timeval now;
    struct timespec outtime;
    gettimeofday(&now, NULL);
    outtime.tv_sec = now.tv_sec + 10;//如果没有等到信号,则超过10s后自动处理.
    outtime.tv_nsec = now.tv_usec * 1000;

    pthread_mutex_lock(&mutex);
    printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
    //2.可以设置超时时间,一旦超过设定时间,自动执行pthread_cond_timedwait函数后边的代码,它不受pthread_cond_signal的影响.
    pthread_cond_timedwait(&cond, &mutex, &outtime);
    printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
    cout << "Thread timed wait for 10s." << endl;
    pthread_mutex_unlock(&mutex);
  });

  sleep(1);
  pthread_cond_signal(&cond);//仅可以唤醒一个线程
  sleep(2);
  thread.join();
  return 0;
}

v3.0 pthread_cond_broadcast和pthread_cond_wait用法

#include <iostream>
#include <thread>
#include <unistd.h>
#include <pthread.h>

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int main() {
  std::thread thread = std::thread([&]() {
    cout << "线程等待信号到来..." << endl;
    pthread_mutex_lock(&mutex);
    //1.如果不调用pthread_cond_signal(&cond)函数发信号,永远死等.
    pthread_cond_wait(&cond, &mutex);
    cout << "Thread timed wait for 10s." << endl;
    pthread_mutex_unlock(&mutex);
  });

  //1.给pthread_cond_wait和pthread_cond_timedwait发信号(它俩是成对出现的),使其后边的代码可以执行.
  sleep(1);
  pthread_cond_broadcast(&cond);//可以唤醒全部线程
  sleep(2);
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
  thread.join();
  return 0;
}

v4.0 pthread_cond_broadcast和pthread_cond_timedwait用法

#include <iostream>
#include <thread>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int main() {
  //1.给pthread_cond_wait和pthread_cond_timedwait发信号(它俩是成对出现的),使其后边的代码可以执行.
    std::thread thread = std::thread([&]() {
    cout << "线程等待信号到来..." << endl;
    
    struct timeval now;
    struct timespec outtime;
    gettimeofday(&now, NULL);
    outtime.tv_sec = now.tv_sec + 10;//如果没有等到信号,则超过10s后自动处理.
    outtime.tv_nsec = now.tv_usec * 1000;

    pthread_mutex_lock(&mutex);
    printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
    //2.可以设置超时时间,一旦超过设定时间,自动执行pthread_cond_timedwait函数后边的代码,它不受pthread_cond_signal的影响.
    pthread_cond_timedwait(&cond, &mutex, &outtime);
    printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
    cout << "Thread timed wait for 10s." << endl;
    pthread_mutex_unlock(&mutex);
  });

  sleep(1);
  pthread_cond_broadcast(&cond);//可以唤醒全部线程
  sleep(2);
  thread.join();
  return 0;
}

v5.0 pthread_cond_wait: 观察pthread_cond_t和pthread_mutex_t结构体中变量:__data、__size、__align变化

pthread_cond_signal和pthread_cond_wait用法

#include <iostream>
#include <thread>
#include <unistd.h>
#include <pthread.h>

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int main() {


  std::thread thread = std::thread([&]() {
    cout << "线程等待信号到来..." << endl;
    printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
    pthread_mutex_lock(&mutex);
    //1.如果不调用pthread_cond_signal(&cond)函数发信号,永远死等.
    pthread_cond_wait(&cond, &mutex);
    printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
    cout << "Thread timed wait for 10s." << endl;
    pthread_mutex_unlock(&mutex);
  });

  //1.给pthread_cond_wait和pthread_cond_timedwait发信号(它俩是成对出现的),使其后边的代码可以执行.
  sleep(1);
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*(reinterpret_cast<int*>(&(cond.__data))));

  printf("%s() [%d], cond.__size = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__size))));
  printf("%s() [%d], cond.__align = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__align))));

  printf("%s() [%d], mutex.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__data))));
  printf("%s() [%d], mutex.__size = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__size))));
  printf("%s() [%d], mutex.__align = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__align))));

  //pthread_cond_signal(&cond);//仅可以唤醒一个线程
  pthread_cond_broadcast(&cond);//可以唤醒全部线程

  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
  printf("%s() [%d], cond.__size = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__size))));
  printf("%s() [%d], cond.__align = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__align))));

  printf("%s() [%d], mutex.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__data))));
  printf("%s() [%d], mutex.__size = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__size))));
  printf("%s() [%d], mutex.__align = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__align))));

  sleep(2);
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));

  thread.join();
  return 0;
}

打印:

线程等待信号到来...
operator()() [16], cond.__data = 0
main() [27], cond.__data = 2
main() [28], cond.__data = 2
main() [30], cond.__size = 2
main() [31], cond.__align = 2
main() [33], mutex.__data = 0
main() [34], mutex.__size = 0
main() [35], mutex.__align = 0
main() [40], cond.__data = 3
main() [41], cond.__size = 3
main() [42], cond.__align = 3
main() [44], mutex.__data = 0
main() [45], mutex.__size = 0
main() [46], mutex.__align = 0
operator()() [20], cond.__data = 3
Thread timed wait for 10s.
main() [49], cond.__data = 3

总结:
在进入线程后,信号没来到之前 cond.__data=0;
当进入main进程时,cond.__data=2;
当pthread_cond_signal(&cond)发送信号后,cond.__data=3,然后线程中的pthread_cond_wait(&cond, &mutex)收到cond.__data=3的信号,
立即释放互斥锁,解除阻塞,程序向下运行,到main函数结束。

v6.0 观察pthread_cond_timedwait:pthread_cond_t和pthread_mutex_t结构体中变量:__data、__size、__align变化

pthread_cond_signal和pthread_cond_timedwait用法

#include <iostream>
#include <thread>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int main() {
  //1.给pthread_cond_wait和pthread_cond_timedwait发信号(它俩是成对出现的),使其后边的代码可以执行.
    std::thread thread = std::thread([&]() {
    cout << "线程等待信号到来..." << endl;

    struct timeval now;
    struct timespec outtime;
    gettimeofday(&now, NULL);
    outtime.tv_sec = now.tv_sec + 10;//如果没有等到信号,则超过10s后自动处理.
    outtime.tv_nsec = now.tv_usec * 1000;

    pthread_mutex_lock(&mutex);
    printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
    //2.可以设置超时时间,一旦超过设定时间,自动执行pthread_cond_timedwait函数后边的代码,它不受pthread_cond_signal的影响.
    pthread_cond_timedwait(&cond, &mutex, &outtime);
    printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
    cout << "Thread timed wait for 10s." << endl;
    pthread_mutex_unlock(&mutex);
  });


  sleep(1);
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*(reinterpret_cast<int*>(&(cond.__data))));


  printf("%s() [%d], cond.__size = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__size))));
  printf("%s() [%d], cond.__align = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__align))));

  printf("%s() [%d], mutex.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__data))));
  printf("%s() [%d], mutex.__size = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__size))));
  printf("%s() [%d], mutex.__align = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__align))));

  //pthread_cond_signal(&cond);//仅可以唤醒一个线程
  pthread_cond_broadcast(&cond);//可以唤醒全部线程
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));
  printf("%s() [%d], cond.__size = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__size))));
  printf("%s() [%d], cond.__align = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__align))));

  printf("%s() [%d], mutex.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__data))));
  printf("%s() [%d], mutex.__size = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__size))));
  printf("%s() [%d], mutex.__align = %d\n",__FUNCTION__,__LINE__,*((int*)(&(mutex.__align))));

  sleep(2);
  printf("%s() [%d], cond.__data = %d\n",__FUNCTION__,__LINE__,*((int*)(&(cond.__data))));

  thread.join();
  return 0;
}

打印

线程等待信号到来...
operator()() [23], cond.__data = 0
main() [33], cond.__data = 2
main() [34], cond.__data = 2
main() [37], cond.__size = 2
main() [38], cond.__align = 2
main() [40], mutex.__data = 0
main() [41], mutex.__size = 0
main() [42], mutex.__align = 0
main() [46], cond.__data = 3
main() [47], cond.__size = 3
main() [48], cond.__align = 3
main() [50], mutex.__data = 0
main() [51], mutex.__size = 0
main() [52], mutex.__align = 0
operator()() [26], cond.__data = 3
Thread timed wait for 10s.
main() [55], cond.__data = 3

总结:
在进入线程后,信号没来到之前 cond.__data=0;
当进入main进程时,cond.__data=2;
当pthread_cond_signal(&cond)发送信号后,cond.__data=3,然后线程中的pthread_cond_wait(&cond, &mutex)收到cond.__data=3的信号,
立即释放互斥锁,解除阻塞,程序向下运行,到main函数结束。

3.总结

所以不管是pthread_cond_signal、pthread_cond_broadcast发送信号给pthread_cond_wait,还是pthread_cond_timedwait,最终信号发生变化的pthread_cond_t是的结构体变量__data字段,是它发生的变化,解除线程的阻塞状态。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/808725.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL | 常用命令示例

MySQL | 常用命令示例 一、启停MySQL数据库服务二、连接MySQL数据库三、创建和管理数据库四、创建和管理数据表五、数据备份和恢复六、查询与优化 MySQL是一款常用的关系型数据库管理系统&#xff0c;广泛应用于各个领域。在使用MySQL时&#xff0c;我们经常需要编写一些常用脚…

M 芯片的 macos 系统安装虚拟机 centos7 网络配置

centos 安装之前把网络配置配好或者是把网线插好 第一步找到这个 第二步打开网络适配器 选择图中所指位置 设置好之后 开机启动 centos 第三步 开机以后 编写网卡文件保存 重启网卡就可以了&#xff0c;如果重启网卡不管用&#xff0c;则重启虚拟机即可 “ ifcfg-ens160 ” 这…

盖子的c++小课堂——第二十一讲:map

前言 时隔一周&#xff0c;我又来更新了^_^&#xff0c;今天都第二十一讲了&#xff0c;前三个板块马上就结束了&#xff0c;也就是小课堂&#xff08;1&#xff09;马上结束了&#xff0c;敬请期待“盖子的c小课堂&#xff08;2&#xff09;”&#xff0c;嘿嘿~~ map 数据容…

QT--day5(网络聊天室、学生信息管理系统)

服务器&#xff1a; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//给服务器指针实例化空间servernew QTcpServer(this); }Widget::~Widget() {delete ui; …

【C#】.Net Framework框架下的Authorize权限类

2023年&#xff0c;第31周&#xff0c;第3篇文章。给自己一个目标&#xff0c;然后坚持总会有收货&#xff0c;不信你试试&#xff01; 在C#的.NET Framework中&#xff0c;你可以使用Authorize类来处理权限认证。Authorize类位于System.Web.Mvc命名空间中&#xff0c;它提供了…

VS创建wsdl服务提供给java调用

文章目录 前言1.c#创建asp.net web服务1.1 创建ASP.NET Web应用程序1.2 添加服务类1.3 定义服务方法1.3 浏览服务1.4 发布服务1.5 IIS部署服务 2.Java中调用服务2.1 用动态客户端工厂类调用2.1.1 引入依赖2.1.2 调用测试代码2.1.3 测试结果 2.2 创建代理类进行调用2.2.1 使用ws…

微软:向量搜索和向量数据库

向量是未来的数据表示 向量搜索 方法 减少距离计算次数 哈希法空间划分树近邻图 SPTAG 混合了kd树和近邻图 Change 大规律向量搜索 内存可扩展 倒排索引 全局量化进行压缩 top1的召回率比较低 基于图的近邻图 SPANN 倒排索引中的问题&#xff1a; 不平衡的聚类方法低…

Python读取csv、Excel文件生成图表

简介 本文章介绍了通过读取 csv 或 Excel 文件内容&#xff0c;将其转换为折线图或柱状图的方法&#xff0c;并写入 html 文件中。 目录 1. 读取CSV文件 1.1. 生成折线图 1.1.1. 简单生成图表 1.1.2. 设置折线图格式 1.2. 生成柱状图 1.2.1. 简单生成图表 1.2.2. 设置柱…

Python-Python基础综合案例:数据可视化 - 折线图可视化

版本说明 当前版本号[20230729]。 版本修改说明20230729初版 目录 文章目录 版本说明目录知识总览图Python基础综合案例&#xff1a;数据可视化 - 折线图可视化json数据格式什么是jsonjson有什么用json格式数据转化Python数据和Json数据的相互转化 pyecharts模块介绍概况如何…

年薪百万的提示词工程师到底在做什么?

&#x1f3c6; 文章目标&#xff1a;了解热门开源项目 &#x1f340; 入门篇&#xff1a;程序员,必须要知道的热门开源项目! ✅ 创作者&#xff1a;熊猫Jay ✨ 个人公众号: 熊猫Jay字节之旅 (文末有链接) &#x1f341; 展望&#xff1a;若本篇讲解内容帮助到您&#xff0c;请帮…

高忆管理:股票投资策略是什么?有哪些?

在进行股票买卖过程中&#xff0c;出资者需求有自己的方案和出资战略&#xff0c;并且主张严格遵从出资战略买卖&#xff0c;不要跟风操作。那么股票出资战略是什么&#xff1f;有哪些&#xff1f;下面就由高忆管理为我们剖析&#xff1a; 股票出资战略简略来说便是能够协助出资…

左值引用与右值引用的区别?右值引用的意义?

左值引用与右值引用的区别&#xff1f;右值引用的意义&#xff1f; 1 区别1.1 功能差异1.2 左值引用1.3 右值引用1.3.1 实现移动语义1.3.2 实现完美转发 2 引用的作用3 区分左值和右值3.1 左值3.2 右值 1 区别 左值引用是对左值的引用&#xff1b;右值引用是对右值的引用。 &…

【Linux】进程通信 — 共享内存

文章目录 &#x1f4d6; 前言1. 共享内存2. 创建共享内存2.1 ftok()创建key值&#xff1a;2.2 shmget()创建共享内存&#xff1a;2.3 ipcs指令&#xff1a;2.4 shmctl()接口&#xff1a;2.5 shmat()/shmdt()接口:2.6 共享内存没有访问控制&#xff1a;2.7 通过管道对共享内存进…

实验六 调度器-实验部分

目录 一、知识点 1.进程调度器设计的目标 1.1.进程的生命周期 1.2.用户进程创建与内核进程创建 1.3.进程调度器的设计目标 2.ucore 调度器框架 2.1.调度初始化 2.2.调度过程 2.2.1.调度整体流程 2.2.2.设计考虑要点 2.2.3.数据结构 2.2.4.调度框架应与调度算法无关…

二十三章:抗对抗性操纵的弱监督和半监督语义分割的属性解释

0.摘要 弱监督语义分割从分类器中生成像素级定位&#xff0c;但往往会限制其关注目标对象的一个小的区域。AdvCAM是一种图像的属性图&#xff0c;通过增加分类分数来进行操作。这种操作以反对抗的方式实现&#xff0c;沿着像素梯度的相反方向扰动图像。它迫使最初被认为不具有区…

【已解决】电脑连上网线但无法上网

文章目录 案例情况解决方案必要的解决方法简要概括详细步骤1、打开控制面板2、打开更改适配器设置3、 找Internet协议版本44、修改配置 可能有用的解决方法 问题解决原理Internet 协议版本 4&#xff08;TCP/IPv4&#xff09;确保IP地址和DNS服务器设置为自动获取 案例情况 网…

Knowledge-QA-LLM: 基于本地知识库+LLM的开源问答系统

⚠️注意&#xff1a;后续更新&#xff0c;请移步README Knowledge QA LLM 基于本地知识库LLM的问答系统。该项目的思路是由langchain-ChatGLM启发而来。缘由&#xff1a; 之前使用过这个项目&#xff0c;感觉不是太灵活&#xff0c;部署不太友好。借鉴如何用大语言模型构建一…

CTF学习路线指南(附刷题练习网址)

前言&#xff1a; PWN,Reverse&#xff1a;偏重对汇编&#xff0c;逆向的理解&#xff1b; Gypto&#xff1a;偏重对数学&#xff0c;算法的深入学习&#xff1b; Web&#xff1a;偏重对技巧沉淀&#xff0c;快速搜索能力的挑战&#xff1b; Mic&#xff1a;则更为复杂&…

devDept Eyeshot 2024 预告-Update-Crack

即将发布的版本 开发商在一个动态的环境中运作&#xff0c;事情可能会发生变化。本页提供的信息旨在概述 devDept 软件产品的总体方向。它仅供参考&#xff0c;不应作为做出任何决定性的依据。devDept Eyeshot 2024软件产品描述的任何特性或功能的开发、发布和时间安排仍由 dev…

css实现渐变边框动画

渐变边框动画 1、实现效果2、实现代码 1、实现效果 2、实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">&…