C++所有锁的讲解、使用场景、相应的C++代码示例

一、互斥锁(Mutex)

1. std::mutex

含义: std::mutex 最基本的互斥锁,当一个线程占用锁时,其他线程必须等待该锁被释放。

使用场景: 当需要保护共享资源不被多个线程同时修改时使用。

代码示例:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;  // 全局互斥锁
int counter = 0; // 共享资源

void attempt_10k_increases() 
{
    for (int i = 0; i < 10000; ++i) 
    {
        mtx.lock();
        ++counter;  // 受保护的操作
        mtx.unlock();
    }
}

int main() 
{
    std::thread threads[10];
    for (int i = 0; i < 10; ++i)
    {
        threads[i] = std::thread(attempt_10k_increases);
    }

    for (auto& th : threads) 
    {
    	th.join();
    }
    
    std::cout << "Result of counter: " << counter << std::endl;
    return 0;
}

输出结果: Result of counter: 100000

解释: 这个程序创建了10个线程,每个线程尝试对counter增加10000次。通过使用std::mutex, 我们确保每次只有一个线程可以增加计数器,避免了数据竞争。

2. std::recursive_mutex

含义: 递归互斥锁,允许同一个线程多次获取同一锁。

使用场景: 在递归函数中需要多次获取同一个锁的情况。

代码示例:

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex rec_mtx;
int count = 0;

void recursive_increment(int level) 
{
    if (level > 0) 
    {
        rec_mtx.lock();
        recursive_increment(level - 1);
        rec_mtx.unlock();
    } 
    else 
    {
        ++count;
    }
}

int main() 
{
    std::thread t(recursive_increment, 10);
    t.join();
    std::cout << "Count is: " << count << std::endl;
    return 0;
}

输出结果: Count is: 1

解释: 这段代码在递归函数recursive_increment中使用std::recursive_mutex。每次调用都会尝试加锁,由于使用的是递归互斥锁,同一线程可以多次成功获取锁。

二、定时锁

1. std::timed_mutex

含义: 允许尝试锁定一定时间,如果在指定时间内没有获取到锁,则线程可以执行其他操作或放弃。

使用场景: 当你不希望线程因等待锁而无限期阻塞时使用。

代码示例:

#include <iostream>
#include <mutex>
#include <chrono>
#include <thread>

std::timed_mutex timed_mtx;

void attempt_lock_for(int id) 
{
    auto now = std::chrono::steady_clock::now();
    if (timed_mtx.try_lock_for(std::chrono::seconds(1))) 
    {
        std::cout << "Thread " << id << " got the lock." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2)); // hold the lock for 2 seconds
        timed_mtx.unlock();
    } 
    else 
    {
        std::cout << "Thread " << id << " couldn't get the lock." << std::endl;
    }
}

int main() 
{
    std::thread threads[2];
    for (int i = 0; i < 2; ++i) 
    {
        threads[i] = std::thread(attempt_lock_for, i);
    }
    for (auto& th : threads) 
    {
    	th.join();
    }
    
    return 0;
}

输出结果:

Thread 0 got the lock.
Thread 1 couldn't get the lock.

解释: 这段代码创建了两个线程,每个线程尝试锁定同一个std::timed_mutex。第一个线程获取锁并持有2秒钟,而第二个线程只尝试1秒钟去获取锁,因此它失败了。

2. std::recursive_timed_mutex

含义: std::recursive_timed_mutex结合了std::recursive_mutexstd::timed_mutex的特点,允许同一个线程多次加锁,并提供了尝试加锁的超时功能。

使用场景: 适用于需要递归锁定资源,并且希望能够设置尝试获取锁的超时时间的场景。这在需要防止线程在等待锁时无限阻塞的复杂递归调用中特别有用。

代码示例:

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::recursive_timed_mutex rt_mtx;

void recursive_access(int level, int thread_id) 
{
    if (rt_mtx.try_lock_for(std::chrono::milliseconds(100))) 
    {
        std::cout << "Thread " << thread_id << " entered level " << level << std::endl;
        if (level > 0) 
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            recursive_access(level - 1, thread_id);
        }
        rt_mtx.unlock();
    } 
    else 
    {
        std::cout << "Thread " << thread_id << " could not enter level " << level << std::endl;
    }
}

int main() {
    std::thread t1(recursive_access, 3, 1);
    std::thread t2(recursive_access, 3, 2);

    t1.join();
    t2.join();

    return 0;
}

输出结果: 输出结果可能会变化,因为线程的执行和锁的获取取决于操作系统的线程调度策略。可能的输出包括两个线程交替进入不同的递归级别,或者一个线程完全执行完毕后另一个线程开始执行。

解释: 在这个示例中,每个线程尝试进入一个递归函数recursive_access,该函数使用std::recursive_timed_mutex。每个线程在进入下一个递归级别之前会尝试获取锁,并设置超时时间为100毫秒。如果一个线程在递归的某个级别成功获取了锁,它会打印信息然后在释放锁之前休眠50毫秒。如果获取锁失败(可能由于另一个线程正在持有锁),它将打印未能进入该级别的消息。

这种类型的锁是非常特定场景下的工具,适用于需要递归锁控制的同时又不希望线程在获取不到锁时无限等待的情况。

三、读写锁(Shared Mutex)

std::shared_mutex

含义: 允许多个线程同时读取资源,但只允许一个线程写入。

使用场景: 适用于读操作远多于写操作的情况。

代码示例:

#include <iostream>
#include <shared_mutex>
#include <thread>

std::shared_mutex shared_mtx;
int data = 0;

void reader_function(int id) 
{
    shared_mtx.lock_shared();
    std::cout << "Reader " << id << " sees data as: " << data << std::endl;
    shared_mtx.unlock_shared();
}

void writer_function(int new_data) 
{
    shared_mtx.lock();
    data = new_data;
    std::cout << "Writer updates data to: " << data << std::endl;
    shared_mtx.unlock();
}

int main() 
{
    std::thread writer(writer_function, 100);
    std::thread readers[10];
    for (int i = 0; i < 10; ++i)
    {
        readers[i] = std::thread(reader_function, i);
    }

    writer.join();
    for (auto& reader : readers)
    {
        reader.join();
    }

    return 0;
}

输出结果: 输出结果可能会有所不同,因为读写顺序由操作系统的线程调度决定。

解释: 本例中,一个写线程在修改数据,多个读线程在同时读数据。通过std::shared_mutex,我们允许多个读操作同时进行,但写操作是独占的。

四、自旋锁

自旋锁在C++标准库中没有直接提供一个专门的类型,但它可以使用原子操作,尤其是std::atomic_flag来实现。自旋锁是一种低级同步机制,适用于锁持有时间非常短的情况。与其他锁不同,当自旋锁无法获取锁时,它将在一个循环中持续检查锁的状态,这意味着它会保持CPU的活跃状态,而不是使线程进入休眠。

含义:自旋锁是一种在等待解锁时使线程保持忙等(busy-wait)的锁,这意味着线程会持续占用CPU时间直到它能获取到锁。

使用场景:自旋锁适用于锁持有时间非常短且线程不希望在操作系统调度中频繁上下文切换的场景。这通常用在低延迟系统中,或者当线程数量不多于CPU核心数量时,确保CPU不会在等待锁时空闲。

自旋锁的代码示例
下面是使用std::atomic_flag实现简单自旋锁的示例:

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>

class SpinLock 
{
private:
    std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;

public:
    void lock() 
    {
        while (lock_flag.test_and_set(std::memory_order_acquire)) 
        {
            // 循环等待,直到锁变为可用状态
        }
    }

    void unlock() 
    {
        lock_flag.clear(std::memory_order_release);
    }
};

SpinLock spinlock;

void work(int id) 
{
    spinlock.lock();
    std::cout << "Thread " << id << " entered critical section." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));  // 模拟工作
    std::cout << "Thread " << id << " leaving critical section." << std::endl;
    spinlock.unlock();
}

int main() 
{
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) 
    {
        threads.emplace_back(work, i);
    }

    for (auto& th : threads) 
    {
        th.join();
    }

    return 0;
}

输出结果

Thread 0 entered critical section.
Thread 0 leaving critical section.
Thread 1 entered critical section.
Thread 1 leaving critical section.
...

解释
在此示例中,SpinLock类使用std::atomic_flag实现。lock方法通过test_and_set在一个循环中尝试设置标志位直到成功,从而实现锁的功能。unlock方法通过clear清除标志位。这保证了在某个时刻只有一个线程可以进入临界区,同时使用了忙等待而不是线程休眠。由于忙等待的性质,自旋锁特别适用于预期锁只被短暂持有的场景。

五、唯一锁(Unique Lock)

std::unique_lock

含义: 比std::lock_guard更灵活的锁,支持延迟锁定、时间锁定、以及在同一作用域中的锁的转移。

使用场景: 需要在复杂控制流中灵活管理锁的情况。

代码示例:

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void print_even(int x) 
{
    if (x % 2 == 0) 
    {
        std::unique_lock<std::mutex> lock(mtx);
        std::cout << x << " is even." << std::endl;
    } 
    else 
    {
        std::cout << x << " is odd." << std::endl;
    }
}

int main() 
{
    std::thread threads[10];
    for (int i = 0; i < 10; ++i) 
    {
        threads[i] = std::thread(print_even, i);
    }
    for (auto& th : threads) 
    {
    	th.join();
    }
    
    return 0;
}

输出结果:

0 is even.
1 is odd.
2 is even.
3 is odd.
...

解释: 此示例中,我们仅在打印偶数时获取锁。std::unique_lock允许在需要时才加锁,这提供了比std::lock_guard更大的灵活性。

附加

1、锁保护(Lock Guard)

std::lock_guard
含义: 自动管理锁的生命周期,确保作用域结束时释放锁。

使用场景: 当你需要确保在当前作用域结束时自动释放锁,以避免死锁。

代码示例:

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void print_block(int n, char c) 
{
    std::lock_guard<std::mutex> lock(mtx);
    for (int i = 0; i < n; ++i) 
    {
        std::cout << c;
    }
    std::cout << '\n';
}

int main() {
    std::thread t1(print_block, 50, '*');
    std::thread t2(print_block, 50, '$');

    t1.join();
    t2.join();

    return 0;
}

输出结果:

**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

或者两行输出的顺序可能会反过来,取决于线程的调度。

解释: 这个示例中,我们使用std::lock_guard来确保在打印过程中互斥锁被持有。这样可以避免输出交错。

2、条件变量

条件变量是一种同步原语,它可以阻塞一个或多个线程,直到某个特定条件为真。条件变量总是与互斥锁(std::mutex)一起使用,以避免竞争条件。基本操作包括:

  • 等待(wait):线程阻塞,并释放其持有的互斥锁,直到另一个线程通知(notify)条件变量。
  • 通知(notify_one/notify_all):解除一个或所有等待线程的阻塞状态。

如何使用条件变量
条件变量用于复杂的同步问题,例如当线程需要等待某些条件(如资源可用或任务完成)满足时。它们不仅用于避免死锁,还用于减少不必要的忙等待,使得线程管理更为高效。

代码示例

假设有一个生产者-消费者场景,其中生产者不能在缓冲区满时生产,消费者不能在缓冲区空时消费:

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> products;

void producer(int id) 
{
    for (int i = 0; i < 5; ++i) 
    {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return products.size() < 5; });
        products.push(i);
        std::cout << "Producer " << id << " produced " << i << std::endl;
        lock.unlock();
        cv.notify_all();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void consumer(int id) 
{
    while (true) 
    {
        std::unique_lock<std::mutex> lock(mtx);
        if (cv.wait_for(lock, std::chrono::seconds(1), [] { return !products.empty(); })) 
        {
            int product = products.front();
            products.pop();
            std::cout << "Consumer " << id << " consumed " << product << std::endl;
            lock.unlock();
            cv.notify_all();
        } 
        else 
        {
            break; // Assume done if no production for 1 second.
        }
    }
}

int main() 
{
    std::thread p1(producer, 1), p2(producer, 2);
    std::thread c1(consumer, 1), c2(consumer, 2);
    p1.join();
    p2.join();
    c1.join();
    c2.join();
    return 0;
}

在这个例子中,我们使用了条件变量来同步生产者和消费者之间的操作:

  • 生产者在队列未满时生产,并在生产后通知消费者;
  • 消费者在队列非空时消费,并在消费后通知生产者。

使用条件变量的优势在于它能够减少资源的浪费,提高线程间的协作效率,特别是在需要频繁等待特定条件的场景中。

  • 14
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 《C 5.11经典代码大全》是一本经典的C语言代码手册,全面收录了440多个常用的、高效的C语言代码,是程序员学习和提高C语言编程的重要参考资料。这本书分为两大部分,第一部分是C编程基础,介绍了C语言的基本语法、数据类型、控制结构、函数、指针等基础知识;第二部分是C编程实践,收录了许多实用的C语言代码,涵盖了文件操作、数据结构、算法、网络编程、系统编程、图形界面编程等多个方面的内容。 这本书的特点在于,每个代码都是经过作者亲自编写和测试的,能够帮助读者更好地理解C语言的知识和应用。此外,全书涵盖了许多常见的问题和场景,如如何获取系统信息、如何处理文件、如何进行网络编程等,适合各种C语言开发使用。同时,本书的代码风格简洁、易于理解,有助于读者学习和掌握高效的C编程技巧。 总之,《C 5.11经典代码大全》是一本值得推荐的C语言编程实践工具书,能够帮助读者提升编程水平、掌握高效的C编程技巧,也为Coder们提供了一份方便的参考手册。无论是初学者还是有经验的程序员,都可以从中受益。 ### 回答2: 《C 5.11经典代码大全》是一本介绍C语言编程的经典工具书。本书收录了众多C语言的优秀示例代码,从最基础的数据类型和运算符、数组、指针,到C语言中的函数、文件操作、结构体和共用体等主题都有详细的讲解。读者不仅可以学习各种C语言的编程知识,还可以通过阅读和实践优秀示例代码来提高自己的编程能力和思维水平。 《C 5.11经典代码大全》的特点是代码实用性强、重视实例。书中的每个代码示例都是非常实用的,可以帮助读者解决实际编程过程中遇到的难题和疑惑。此外,本书还特别注重实例的讲解和练习,通过实践练习能够帮助读者更好地理解和掌握各种C语言的编程技巧。 这本书适合初学者以及有一定编程基础的读者阅读。对于初学者来说,《C 5.11经典代码大全》是一个非常好的学习工具,可以帮助他们逐步掌握C语言的基础知识以及编程思维。对于已有一定编程基础的读者来说,本书也有很多实用的示例代码可以供他们借鉴和应用。 总的来说,《C 5.11经典代码大全》是一本非常实用的C语言编程工具书,值得广大读者学习和应用。 ### 回答3: 《C 5.11经典代码大全》是一本介绍C语言编程中经典代码的书籍,作者是Steve Oualline。本书汇集了大量的C程序代码,并且通过详细的解释和注释,让读者了解C编程的编写方式与技巧。这本书基本包含了C语言中的所有基础和高级知识,以及各种实际应用场景中的常见问题和解决方案。 这本书的主要特点在于实用性和实用性。它包含了众多的范例,而且这些范例都是基于实际编程项目开发中的问题而展开。此外,本书的范例代码都经过了作者的严密审阅和测试,因此具有很高的实用性。这些代码涵盖了众多领域,包括基本的数据结构、算法、文件处理、网络编程、多线程等。初学者可以通过学习这些代码来快速入门,而专业人士则可以借鉴其中的思路和技巧来解决实际开发中的问题。 总之,《C 5.11经典代码大全》是一本不可多得的好书,对所有学习和从事C语言编程的人士都是非常有益的。它不仅可以帮助读者提高对C语言的全面理解,也可以帮助读者掌握C语言编程中的技能与技巧。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Warren++

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

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

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

打赏作者

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

抵扣说明:

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

余额充值