Linux线程同步与互斥

?linux线程互斥

临界资源:多线程执行流共享的资源就叫做临界资源临界区:每个线程内部,访问临界资源的代码,就叫做临界区互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

  如果不能保持互斥,那么会发生一些不合逻辑的事情,以下面这段多线程抢票代码为例:

代码语言:javascript代码运行次数:0运行复制

#include #include #include "thread.hpp" // 自己实现的线程封装using namespace ThreadModule;// 数据不一致int g_tickets = 10000; // 共享资源,没有保护的, 多线程同时访问void route(int &tickets){    while (true)    {        if(tickets>0) // 票数小于0, 终止抢票        {            usleep(1000);            printf("get tickets: %dn", tickets);            tickets--;        }        else        {            break;        }    }}const int num = 4;int main(){    // std::cout << "main: &tickets: " << &g_tickets << std::endl;    std::vector<Thread> threads;    // 1. 创建一批线程    for (int i = 0; i < num; i++)    {        std::string name = "thread-" + std::to_string(i + 1);        threads.emplace_back(route, g_tickets, name);    }    // 2. 启动 一批线程    for (auto &thread : threads)    {        thread.Start();    }    // 3. 等待一批线程    for (auto &thread : threads)    {        thread.Join();        std::cout << "wait thread done, thread is: " << thread.name() << std::endl;    }    return 0;}
Linux线程同步与互斥

  这里线程对同一个共享资源进行操作,进行并发执行类似 “抢票” 的模式,但是最后得到的数据却发现,抢票居然还有负数?这种情况我们称为 数据不一致。

  这个问题是怎么产生怎么导致的呢?首先我们先要了解一个概念:原子性,前面我们说,原子性只有两态,要么已完成,要么未完成。实际上,在编程的角度来说,原子性指的是汇编层面只有一条语句。比如对一个内置类型进行赋值操作,在汇编层面其实就是一条move指令。所以其是原子的。

  了解了上述概念之后,我们再来看一看代码的逻辑结构,在route函数里,我们对tickets进行了判断,而判断是逻辑运算,需要在CPU内进行操作。

立即进入“豆包AI人工智官网入口”;

立即学习“豆包AI人工智能在线问答入口”;

Linux线程同步与互斥

  判断完成后,刚刚进入内部,执行usleep()函数,所以此时线程就被切换,进入到等待队列。假设此时是thread-1在跑,又因为tickets被保存到寄存器当中,而thread-1此时要进行线程切换则需要带走thread-1的数据,则此时thread-1把寄存器中的tickets带走了。(线程等待结束后才会继续执行后续代码)

  随后thread-2也开始执行此函数,因为上一个thread-1线程遇到了usleep,所以后续的tickets- -,以及total- – 都是没有执行的。也就是说上一次对tickets操作后tickets值并没有变,所以此时thread-2同样将内存中的tickets加载到寄存器当中,同样,tickets此时的值还是1,同样thread-2遇到usleep,那么thread-2也要带着自己的数据到等待队列当中。

Linux线程同步与互斥

  把全局变量加载到CPU不是本质,本质是 将共享的全局变量加载到寄存器使得当前线程私有化共享全局变量。而此时寄存器的值又没有被写回,所以此时thread-2也进入到等待队列。同理,周而复始,thread-3 4都是如此。

  而当等待队列等待完成后,所有线程都开始执行后续的代码,之前阻塞到printf,这里printf不影响tickets所以忽略。后续代码执行到 tickets-- ⇒ tickets = tickets - 1; 此步操作不是原子的,因为它需要经历:1、从内存读取到CPU. 2、CPU内部进行- -操作. 3、写回到内存中, 那么此时问题就出来了。

  当thread-1已经将tickets进行了–,并且将其写回到了内存。那么接下来thread-2等待完成又对tickets进行–,此时CPU中的tickets已经变为0了,所以–过后tickets变为了-1,再次将其写回到内存当中。周而复始,使得原本正常的抢票,最后却变为了负数。

?线程锁

  上面我们已经把问题给搞明白了,接下来我们需要解决问题,如何解决这种线程问题呢?通常的解决方案是对线程进行加锁。

✈️互斥量Mutex

  大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。多个线程并发的操作共享变量,会带来一些问题。为了解决上述问题:

代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

  为此,Linux给我们提供了互斥锁,首先我们先来认识一下这些接口:

Linux线程同步与互斥

?初始化互斥量的两种方式

  使用 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 宏进行初始化互斥量,那么这把锁就可以直接使用了。

代码语言:javascript代码运行次数:0运行复制

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

函数参数:

mutex:要初始化的互斥量 attr:NULL?申请锁方式

  不论我们使用哪种方式定义上面的锁,我们都可以对这把锁进行上锁 pthread_lock():

Linux线程同步与互斥

申请锁接口:

代码语言:javascript代码运行次数:0运行复制

int pthread_mutex_lock(pthread_mutex_t *mutex);

  使用该接口只有三种结果:

申请成功,函数会返回,允许继续向后执行。申请失败,函数会阻塞,不允许向后运行。函数调用失败,出错返回。

尝试申请锁:

代码语言:javascript代码运行次数:0运行复制

int pthread_mutex_trylock(pthread_mutex_t *mutex);

  尝试申请锁,与pthread_mutex_lock()唯一不同的是,当申请锁失败之后,不会进行阻塞等待,而是直接出错返回,并设置错误码返回出错原因。

?解除与销毁锁

解除互斥锁:

代码语言:javascript代码运行次数:0运行复制

int pthread_mutex_unlock(pthread_mutex_t *mutex);

  有加锁必然有解锁,当线程在临界资源内执行完毕后,需要释放当前锁,让其他线程进入,所以需要释放锁。

销毁互斥锁:

代码语言:javascript代码运行次数:0运行复制

int pthread_mutex_destroy(pthread_mutex_t *mutex);

使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁不要销毁一个已经加锁的互斥量已经销毁的互斥量,要确保后面不会有线程再尝试加锁

✈️问题解决及线程饥饿

  出现数据不一致问题的本质是,多个执行流并发访问全局数据的代码所导致的!访问公共资源的代码,我们称为临界区。

  我们加锁的本质是把并行的执行流改变为串行的执行流,而对临街资源的保护实质上就是对临街区代码的加解锁。

代码语言:javascript代码运行次数:0运行复制

#include #include #include "thread.hpp"using namespace ThreadModule;// 数据不一致int g_tickets = 10000; // 共享资源,没有保护的, 多线程同时访问class ThreadData{public:    ThreadData(int tickets, const std::string &name):_tickets(tickets), _name(name), _total(0)    {}    ~ThreadData()    {}public:    int &_tickets; // 所有的线程最后都会引用同一个全局的g_tickets    std::string _name;    int _total;};pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;// 创建互斥量void route(ThreadData *td){    while (true)    {        // 加锁力度越细越好        pthread_mutex_lock(&gmutex);// 上锁        if(td->_tickets>0)        {            usleep(1000);            printf("%s running, get tickets: %dn",td->_name.c_str(),  td->_tickets);            td->_tickets--;            pthread_mutex_unlock(&gmutex);// 解锁            td->_total++;// 将解锁放在此句后面也是可以的,只不过这里的total已经不属于临界区了,所以如果要严格按照规则加锁解锁,就在上一句进行解锁        }        else        {            break;        }    }}const int num = 4;// 创建线程数int main(){    // std::cout << "main: &tickets: " << &g_tickets << std::endl;    std::vector<Thread> threads;    std::vector datas;    // 1. 创建一批线程    for (int i = 0; i < num; i++)    {        std::string name = "thread-" + std::to_string(i + 1);        ThreadData* td = new ThreadData(g_tickets, name);        threads.emplace_back(route, td, name);        datas.emplace_back(td);    }    // 2. 启动 一批线程    for (auto &thread : threads)    {        thread.Start();    }    // 3. 等待一批线程    for (auto &thread : threads)    {        thread.Join();        // std::cout << "wait thread done, thread is: " << thread.name() << std::endl;    }    // 4. 输出统计数据    for(auto & data:datas)    {        std::cout <_name << " : " <_total << std::endl;        delete data;    }    return 0;}
Linux线程同步与互斥

  这样加锁了之后,就不会再出现之前的情形,数据也就正常了。但是如果你是CentOS的用户的话,是有一些bug的,因为CentOS环境中,某些线程的竞争能力太强了,以至于得到的结果往往只有一个线程有结果,其他线程为0,这是因为在CentOS中对线程调度的算法没有Unbuntu的新,也就是没有Ubuntu的算法好。

  所以,又能得出另一个结论:多线程加锁,这些多线程对锁的竞争是自由的。如果竞争能力太强的线程,会导致其他线程抢不到锁,也就造成了线程饥饿问题!我所说的CentOS的这种行为就是竞争饥饿问题。

✈️互斥锁的底层实现 经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。现在我们把lock和unlock的伪代码改一下

Linux线程同步与互斥

  swap或者exchange可以交换寄存器和内存单元中的值,第一句movb 0, al,把左值赋值给寄存器al,第二步xchgb把寄存器内的值和Mutex锁进行交换,随后判断 寄存器内的内容是否>0,如果是则返回0,表示加锁成功,否则就挂起等待, 表示当前锁被线程等待,等待完成继续执行锁。

  解锁的过程,此时线程已经执行完毕,把寄存器中的值重新放进内存的mutex变量中,表示当前锁已经释放。下图或许能帮助你更好的理解这一过程:

Linux线程同步与互斥

  为什么线程能做这件事呢?我们之前说过,CPU寄存器内部的数据,保存了线程的硬件上下文,而数据在内存里,所有线程都能够访问,属于共享的,但是如果转移到CPU内部的寄存器中,就属于一个线程私有了。

  上图中,线程1因为某些原因需要线程切换,进入等待队列。那么此时线程1需要把自己的上下文数据带走,其实就是把寄存器当中保存的值带走,并且没有对内存交换的0进行写回,也就是说此时内存中的mutex是0,那么线程2在交换mutex到寄存器当中,就会进行状态检测,此时检测到状态为0,说明当前已经有人占用锁了,则线程2进入到挂起状态,后来的线程依旧会如此,直到第一个线程执行完毕将锁释放。

  所以上述所谓的交换就显得尤为重要,这里的交换指的不是单纯的拷贝,而是所有线程在争锁的时候只有一个值,而这个值往往就是那把锁。交换过程只有一条汇编语句,所以 交换过程是原子的,那么就能保证交换时不会发生线程切换这样的事情。

  一个线程在访问临界区时,对于其他线程来说,1、锁被释放。2、曾经没有申请到锁正在挂起状态。此时当前线程访问临街资源是加了锁的,对其他线程来说这一过程是原子的,所以说此时访问临界区资源是线程安全的。

?线程同步

  主线开始前,我们先来听一个故事:

Linux线程同步与互斥

但是呢这个时候阿飞回头一看,阿飞刚出电话亭,就看到密密麻麻站满了人,“这吃完饭再来不得到猴年马月才能打上电话?” 于是阿飞咬咬牙,大不了中午不吃饭了,说完,因为他距离门最近,他又进去把门关了,又叙了两个小时。随后阿飞痛快的出门,可是刚出门就想起来自己没生活费了,然后又急忙转身进入电话亭把门关上,又给家里打了电话。这样来来回回好几次,一直占着电话亭。 此时电话亭外面的人不乐意了,“怎么还xx的不出来,再不出来劳资见你一次打你一次!”,阿飞眼看着局势不对,也不敢出电话亭,于是就拨通了警察局的电话,警察来了之后,了解了大致情况。于是在电话亭这里设立了警戒线,并且装上了高清摄像头,并规定:每个人来到这里以后必须要排队,并且打完电话的人不能再次直接进入,必须从队尾重新排队打电话。

  其实上面这个故事就是今天的主线,线程同步,为什么这么说呢?我们把人比作线程,在警察来之前,线程一直在占用这个锁,导致其他线程没办法拿到锁,一直处于等待状态,就会产生线程饥饿问题。而第二种情况,每个线程在没拿到锁之前都需要排队等待,并且拿到锁的线程如果要二次进入则需要重新到队尾排队。

  而上述的过程基本上做到了让不同线程在保证电话亭安全的前提下,让所有的线程访问临界资源具有了一定的顺序性。这个工作我们称为 线程同步。

同步:在保证 数据安全 的前提下,让线程能够按照某种特定的顺序访问 临界资源,从而有效避免 饥饿问题,叫做 同步。

✈️条件变量

  实现线程同步,我们常用做法是使用条件变量。这里的条件变量可不是环境变量,那什么是条件变量呢?

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

  例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

✈️条件变量函数

条件变量初始化(动态,局部条件变量):

代码语言:javascript代码运行次数:0运行复制

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrictattr);

函数参数:

cond:要初始化的条件变量 attr:NULL

静态,全局条件变量初始化:

代码语言:javascript代码运行次数:0运行复制

pthread_cond_t cond cond = PTHREAD_COND_INTIALIZER;

  这里与互斥锁规则相似,不再过多赘述。

销毁条件变量:

代码语言:javascript代码运行次数:0运行复制

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足:

代码语言:javascript代码运行次数:0运行复制

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

函数参数:

cond:要在这个条件变量上等待(队列内等待)mutex:互斥量

唤醒线程:

代码语言:javascript代码运行次数:0运行复制

int pthread_cond_broadcast(pthread_cond_t *cond);// 唤醒所有在cond等待下的线程int pthread_cond_signal(pthread_cond_t *cond);// 唤醒一个线程

  以上接口的返回值,全部都是:返回0为成功,失败设置错误码。要学习条件变量实际上上面这些接口就足够了。

✈️条件变量示例

  这里使用全局条件变量,全部使用接口调用的形式展示条件变量的作用: 创建一个主控线程,3个附属线程,对三个附属线程进行cond等待,通过主控线程唤醒这些线程(全部唤醒和单独唤醒)。

  main函数内定义一个接收tid的数组,一函数调用的形式分别创建一个主控线程和多个附属线程:

代码语言:javascript代码运行次数:0运行复制

int main(){    std::vector tids;    StartMaster(&tids);// 主控线程    StartSlaver(&tids);// 其他线程    WaitThread(tids);// 线程等待    return 0;}

  添加需要的头文件,以及设置全局条件变量与全局互斥锁:

代码语言:javascript代码运行次数:0运行复制

#include #include #include #include #include pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;// 全局条件变量pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;// 全局互斥量

  创建附属线程,默认创建3个附属线程,所有附属线程执行同一回调SlaverCore,回调内将所有线程在临界区内加锁并等待,此时线程锁gmutex释放 线程进入cond等待队列,等待主控线程唤醒,下一个线程重复此步操作,直至所有现成进入到cond等待队列,等待主控唤醒:

代码语言:javascript代码运行次数:0运行复制

void* SlaverCore(void* args){    std::string name = static_cast(args);    while(true)    {        // 1. 加锁        pthread_mutex_lock(&gmutex);        // 2. 一般条件变量是在加锁和解锁之间使用        pthread_cond_wait(&gcond, &gmutex);// gmutex: 这个是用来释放的[前一半],进入等待队列,此时锁被释放        std::cout << "当前被叫醒的线程是:" << name << std::endl;        pthread_mutex_unlock(&gmutex);    }    return nullptr;}void StartSlaver(std::vector *tidsptr, int threadnum = 3){    for(int i = 0; i < threadnum; ++i)    {        char *name = new char[64];// 每一个线程都需要new 一个新名字,否则很可能会出现线程覆盖问题        snprintf(name, 64, "slaver-%d", i + 1);        pthread_t tid;        int n = pthread_create(&tid, nullptr, SlaverCore, name);        if(n == 0)        {            std::cout << "create sucess: " << name <emplace_back(tid);        }    }}

  主控线程,创建主控线程,执行主控回调,主控回调函数内,休眠三秒确保所有附属线程进入等待队列,在循环里可选择的将所有线程选择全部唤醒或者隔一秒唤醒一个线程:

代码语言:javascript代码运行次数:0运行复制

void* MasterCore(void *args)// call back func{    sleep(3);    std::cout << "master start work..." << std::endl;    std::string name = static_cast(args);    while(true)    {        // pthread_cond_signal(&gcond);// 一次唤醒1个线程        pthread_cond_broadcast(&gcond);// 广播唤醒所有的线程        std::cout << "master awake a thread..." << std::endl;        sleep(1);    }    return nullptr;}void StartMaster(std::vector *tidsptr)// main contrl thread{    pthread_t tid;    int n = pthread_create(&tid, nullptr, MasterCore, (void*)"Master Thread");    if(n == 0)    {        std::cout << "create master success" <emplace_back(tid);}

  main-thread阻塞等待回收所有线程:

代码语言:javascript代码运行次数:0运行复制

void WaitThread(std::vector &tids){    for(auto & tid : tids)    {        pthread_join(tid, nullptr);    }}

主控线程一次性全部唤醒等待队列的线程:

Linux线程同步与互斥

主控线程每隔一秒唤醒一个线程:

Linux线程同步与互斥

以上就是Linux线程同步与互斥的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/12239.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月19日 02:29:23
下一篇 2025年11月19日 03:01:02

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • SASS 中的 Mixins

    mixin 是 css 预处理器提供的工具,虽然它们不是可以被理解的函数,但它们的主要用途是重用代码。 不止一次,我们需要创建多个类来执行相同的操作,但更改单个值,例如字体大小的多个类。 .fs-10 { font-size: 10px;}.fs-20 { font-size: 20px;}.fs-…

    2025年12月24日
    000
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000
  • 使用CSS mask属性指定图片URL时,为什么浏览器无法加载图片?

    css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…

    2025年12月24日
    000
  • 如何用CSS Paint API为网页元素添加时尚的斑马线边框?

    为元素添加时尚的斑马线边框 在网页设计中,有时我们需要添加时尚的边框来提升元素的视觉效果。其中,斑马线边框是一种既醒目又别致的设计元素。 实现斜向斑马线边框 要实现斜向斑马线间隔圆环,我们可以使用css paint api。该api提供了强大的功能,可以让我们在元素上绘制复杂的图形。 立即学习“前端…

    2025年12月24日
    000
  • 图片如何不撑高父容器?

    如何让图片不撑高父容器? 当父容器包含不同高度的子元素时,父容器的高度通常会被最高元素撑开。如果你希望父容器的高度由文本内容撑开,避免图片对其产生影响,可以通过以下 css 解决方法: 绝对定位元素: .child-image { position: absolute; top: 0; left: …

    2025年12月24日
    000
  • CSS 帮助

    我正在尝试将文本附加到棕色框的左侧。我不能。我不知道代码有什么问题。请帮助我。 css .hero { position: relative; bottom: 80px; display: flex; justify-content: left; align-items: start; color:…

    2025年12月24日 好文分享
    200
  • 前端代码辅助工具:如何选择最可靠的AI工具?

    前端代码辅助工具:可靠性探讨 对于前端工程师来说,在HTML、CSS和JavaScript开发中借助AI工具是司空见惯的事情。然而,并非所有工具都能提供同等的可靠性。 个性化需求 关于哪个AI工具最可靠,这个问题没有一刀切的答案。每个人的使用习惯和项目需求各不相同。以下是一些影响选择的重要因素: 立…

    2025年12月24日
    300
  • 如何用 CSS Paint API 实现倾斜的斑马线间隔圆环?

    实现斑马线边框样式:探究 css paint api 本文将探究如何使用 css paint api 实现倾斜的斑马线间隔圆环。 问题: 给定一个有多个圆圈组成的斑马线图案,如何使用 css 实现倾斜的斑马线间隔圆环? 答案: 立即学习“前端免费学习笔记(深入)”; 使用 css paint api…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信