锁------mutex

        互斥锁(mutex)是一种用于多线程编程中,以防止多个线程同时访问共享资源的同步机制。其主要作用是保证在同一时间内,只有一个线程能够访问到共享资源或执行特定的代码段。

1.互斥锁的主要作用包括:

  1. 防止数据竞争: 当多个线程尝试同时读写同一数据时,可能会导致数据不一致或损坏。互斥锁可以确保一次只有一个线程能够访问该数据。

  2. 保证操作的原子性: 某些操作可能由多个步骤组成,如果这些步骤被其他线程中断,可能会导致错误。互斥锁可以保证这些操作的原子性,即操作要么全部完成,要么全部不做。

  3. 协调线程对共享资源的访问: 互斥锁可以用来控制线程对共享资源(如文件、数据库连接等)的访问,确保资源在同一时间内只被一个线程使用。

  4. 避免死锁: 虽然互斥锁可以防止数据竞争,但如果不恰当使用,也可能导致死锁。死锁是指两个或多个线程互相等待对方释放资源,从而导致程序无法继续执行的情况。因此,合理设计锁的获取和释放机制是避免死锁的关键。

  5. 实现线程间的同步: 互斥锁可以与其他同步机制(如条件变量)配合使用,实现线程间的同步。例如,一个线程可以在满足特定条件之前等待,直到另一个线程通过互斥锁通知它条件已满足。

2.使用互斥锁的注意事项:

  • 避免锁的过度使用:过度使用互斥锁可能会导致性能下降,因为线程需要等待锁的释放。

  • 避免锁的嵌套:嵌套锁(即在一个已锁定的代码块中尝试获取另一个锁)可能导致死锁。

  • 确保锁的释放:确保在函数退出或异常发生时,锁能够被正确释放。

  • 使用锁的粒度:选择适当的锁粒度,即锁保护的代码范围。过大或过小的锁粒度都可能影响性能。

3.应用场景 

1.创建一个虚拟购票系统,多线程来执行,让ticket数量--

#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<unistd.h>
#include<pthread.h>

int ticket = 10000;

void *routine(void *arg)
{
    char *id = (char*)arg;
    while(1)
    {
        
        if(ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket: %d\n", id, ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
    return nullptr;
}

int main()
{
    pthread_t t1, t2, t3, t4;

    pthread_create(&t1, nullptr, routine, (void *)"thread 1");
    pthread_create(&t2, nullptr, routine, (void *)"thread 2");
    pthread_create(&t3, nullptr, routine, (void *)"thread 3");
    pthread_create(&t4, nullptr, routine, (void *)"thread 4");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);


    return 0;
}

2.未使用锁,会造成票变成负数

3.ticket变成负数的原因 

        四个线程(t1, t2, t3, t4)几乎同时运行,每个线程都试图访问和修改 ticket 变量。由于没有使用互斥锁(mutex)或其他同步机制来保护对 ticket 的访问,以下情况可能发生:

  1. 检查-修改-写入(Check-Modify-Write)竞态条件

    • 线程1检查 ticket(假设为 5)。

    • 线程2也检查 ticket(也是 5)。

    • 线程1 将 ticket 减 1,变为 4,并写回。

    • 线程2 也将 ticket 减 1,但使用的是旧值 5,结果写回的是 4(而不是预期的 3)。

    • 这导致 ticket 被多次减 1,最终可能变为负数。

解决方案

为了解决这个问题,需要确保每次只有一个线程可以修改 ticket。这可以通过使用互斥锁(mutex)来实现。        

 

#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<unistd.h>
#include<pthread.h>

int ticket = 10000;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *routine(void *arg)
{
    char *id = (char*)arg;
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket: %d\n", id, ticket);
            ticket--;
            pthread_mutex_unlock(&lock);
        }
        else
        {   
            pthread_mutex_unlock(&lock);
            break;
        }
    }
    return nullptr;
}

int main()
{
    pthread_t t1, t2, t3, t4;

    pthread_create(&t1, nullptr, routine, (void *)"thread 1");
    pthread_create(&t2, nullptr, routine, (void *)"thread 2");
    pthread_create(&t3, nullptr, routine, (void *)"thread 3");
    pthread_create(&t4, nullptr, routine, (void *)"thread 4");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);


    return 0;
}

4.加了锁之后为什么运行速度变慢了

1. 线程阻塞和唤醒的开销

当一个线程尝试获取一个已经被其他线程持有的锁时,该线程会被阻塞,直到锁被释放。线程的阻塞和唤醒涉及到操作系统的上下文切换,这是一个相对昂贵的操作,因为它需要保存和加载线程的执行状态。

2. 减少并行性

锁的存在限制了程序的并行性。当线程必须等待获取锁时,它不能执行,这意味着在多核处理器上可能有很多核心没有被充分利用。如果多个线程频繁地请求同一把锁,它们可能会相互阻塞,导致程序运行效率降低。

3. 锁竞争

如果多个线程频繁地访问同一把锁,可能会导致锁竞争。锁竞争会增加系统的开销,因为线程需要不断地检查锁的状态(是否可用)。在高竞争的情况下,线程可能大部分时间都在等待锁,而不是执行实际的工作。

4. 死锁风险

虽然死锁不直接导致性能问题,但它会使程序完全停止执行。为了避免死锁,程序可能需要实现更复杂的逻辑来管理锁的获取和释放,这会增加代码的复杂性和执行时间。

5. 锁的粒度

锁的粒度(即锁保护的代码范围)也会影响性能。细粒度锁(保护小范围代码)可以减少锁的竞争,但可能需要更多的锁,增加了管理的复杂性。粗粒度锁(保护大范围代码)可以减少锁的数量,但增加了线程阻塞的可能性。

优化策略

为了减少锁对性能的影响,可以考虑以下策略:

  • 减少锁的粒度:尽量缩小锁保护的代码范围,减少线程等待锁的时间。

  • 使用无锁编程技术:如果可能,使用原子操作或其他并发控制机制来避免使用锁。

  • 锁分离:将一个大锁分解为多个小锁,减少锁竞争。

  • 锁的选择:根据具体情况选择合适的锁类型,如读写锁(读写操作分离)可以提高读操作的并行性。

  • 避免锁的嵌套:嵌套锁可能导致死锁,应尽量避免。

  • 性能分析:使用性能分析工具来识别性能瓶颈,优化锁的使用。

5.锁的原理

(1)硬件:把时间中断

(2)软件:汇编指令swap或exchange,使寄存器和内存单元的数据交换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pipip.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值