1、应用层的锁和内核中的锁区别

1-1、特性

1-1-1、 内核锁(Kernel Locks)

  • 作用:内核锁用于保护内核数据结构,防止多个内核线程或进程同时访问和修改这些数据结构,从而避免数据不一致或竞争条件。
  • 类型
    • 自旋锁(Spinlock):当一个线程试图获取已经被占用的自旋锁时,它会不断循环(自旋)直到锁可用。适用于持有时间短的场景。
    • 互斥锁(Mutex):当一个线程试图获取已经被占用的互斥锁时,它会被阻塞,直到锁可用。适用于持有时间较长的场景。
    • 读写锁(Read-Write Lock):允许多个读操作同时进行,但写操作是独占的。适用于读多写少的场景。
  • 使用场景:内核锁主要用于内核空间,保护内核数据结构,确保内核的稳定性和一致性。

1-1-2、 应用层锁(Application-Level Locks)

  • 作用:应用层锁用于保护应用程序中的共享数据,防止多个线程或进程同时访问和修改这些数据,从而避免数据不一致或竞争条件。
  • 类型
    • 互斥锁(Mutex):类似于内核中的互斥锁,用于保护共享资源,确保同一时间只有一个线程可以访问。
    • 读写锁(Read-Write Lock):允许多个读操作同时进行,但写操作是独占的。
    • 条件变量(Condition Variable):用于线程间的通信,允许一个线程等待某个条件成立后再继续执行。
    • 信号量(Semaphore):用于控制对共享资源的访问,可以是二进制信号量(类似于互斥锁)或多级信号量。
  • 使用场景:应用层锁主要用于用户空间,保护应用程序中的共享数据,确保应用程序的稳定性和一致性。

1-1-3、 关系

  • 隔离性:内核锁和应用层锁在功能上是隔离的。内核锁用于保护内核数据结构,而应用层锁用于保护用户空间的数据。
  • 互操作:虽然内核锁和应用层锁在功能上是隔离的,但在某些情况下,它们可能会相互影响。例如,当一个线程在内核空间中持有锁时,如果该线程被调度到用户空间执行应用层代码,应用层代码可能会尝试获取锁,这需要确保锁的粒度和使用方式不会导致死锁或其他并发问题。
  • 性能考虑:内核锁和应用层锁的性能特性不同。内核锁通常更高效,但只能在内核空间中使用;应用层锁则可以在用户空间中使用,但可能需要更多的系统调用来实现。

总结

内核锁和应用层锁虽然在功能和使用场景上有明显的区别,但它们都是为了保护共享数据,避免并发问题。在设计并发程序时,需要根据具体的需求选择合适的锁类型,并确保锁的使用不会导致死锁或其他并发问题。

2、自旋锁

自旋锁(Spinlock)是一种同步机制,用于保护共享资源,确保同一时间只有一个线程可以访问该资源。自旋锁的基本思想是当一个线程试图获取已经被占用的锁时,它会不断循环(自旋)直到锁可用。自旋锁适用于持有时间短的场景,因为它不会使线程进入睡眠状态,从而避免了线程切换的开销。

在不同的操作系统和编程环境中,自旋锁的API可能会有所不同。以下是一些常见的操作系统和编程环境中的自旋锁API示例:

2-1、api

2-1-1、 Linux 内核中的自旋锁

在Linux内核中,自旋锁的API定义在<linux/spinlock.h>头文件中。以下是一些常用的自旋锁API:

  • 定义自旋锁

    1
    spinlock_t my_lock;
  • 初始化自旋锁

    1
    spin_lock_init(&my_lock);
  • 获取自旋锁

    1
    spin_lock(&my_lock);
  • 释放自旋锁

    1
    spin_unlock(&my_lock);
  • 带中断保护的自旋锁

    1
    2
    3
    4
    unsigned long flags;
    spin_lock_irqsave(&my_lock, flags);
    // 临界区代码
    spin_unlock_irqrestore(&my_lock, flags);

2-1-2、 POSIX 线程(Pthreads)中的自旋锁

在POSIX线程中,自旋锁的API定义在<pthread.h>头文件中。以下是一些常用的自旋锁API:

  • 定义自旋锁

    1
    pthread_spinlock_t my_lock;
  • 初始化自旋锁

    1
    pthread_spin_init(&my_lock, PTHREAD_PROCESS_PRIVATE);
  • 获取自旋锁

    1
    pthread_spin_lock(&my_lock);
  • 释放自旋锁

    1
    pthread_spin_unlock(&my_lock);
  • 销毁自旋锁

    1
    pthread_spin_destroy(&my_lock);

2-1-3、 C++11 标准库中的自旋锁

在C++11标准库中,并没有直接提供自旋锁的实现,但可以使用std::atomic来实现自旋锁。以下是一个简单的自旋锁实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <atomic>

class SpinLock {
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
// 自旋等待
}
}

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

private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};

示例代码

以下是一个使用Linux内核自旋锁的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spinlock.h>

static spinlock_t my_lock;
static int shared_resource = 0;

static int __init my_module_init(void) {
spin_lock_init(&my_lock);
printk(KERN_INFO "Module loaded\n");
return 0;
}

static void __exit my_module_exit(void) {
printk(KERN_INFO "Module unloaded\n");
}

static void critical_section(void) {
spin_lock(&my_lock);
// 临界区代码
shared_resource++;
printk(KERN_INFO "Shared resource: %d\n", shared_resource);
spin_unlock(&my_lock);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple module with spinlock");

在这个示例中,spin_lockspin_unlock用于保护对shared_resource的访问,确保同一时间只有一个线程可以修改该资源。

总结

自旋锁的API在不同的操作系统和编程环境中有所不同。在Linux内核中,自旋锁的API定义在<linux/spinlock.h>中;在POSIX线程中,自旋锁的API定义在<pthread.h>中;在C++11标准库中,可以使用std::atomic来实现自旋锁。选择合适的自旋锁API取决于具体的编程环境和需求。

3、原子操作

某个值的原子操作
原子操作(Atomic Operations)是一种在多线程环境中对共享数据进行无锁操作的技术,确保操作的原子性,即操作是不可分割的,不会被其他线程中断。原子操作通常用于实现锁-free 数据结构或提高并发性能。以下是一些常见操作系统和编程环境中的原子操作API示例。

3-1、api

3-1-1、 Linux 内核中的原子操作

在Linux内核中,原子操作的API定义在<linux/atomic.h>头文件中。以下是一些常用的原子操作API:

  • 定义原子变量

    1
    atomic_t my_atomic_var;
  • 初始化原子变量

    1
    atomic_set(&my_atomic_var, 0);
  • 读取原子变量的值

    1
    int value = atomic_read(&my_atomic_var);
  • 增加原子变量的值

    1
    atomic_inc(&my_atomic_var);
  • 减少原子变量的值

    1
    atomic_dec(&my_atomic_var);
  • 增加并返回新值

    1
    int new_value = atomic_add_return(5, &my_atomic_var);
  • 减少并返回新值

    1
    int new_value = atomic_sub_return(5, &my_atomic_var);
  • 比较并交换(Compare and Swap, CAS)

    1
    int old_value = atomic_cmpxchg(&my_atomic_var, old, new);

3-1-2、 POSIX 线程(Pthreads)中的原子操作

在POSIX线程中,原子操作的API定义在<stdatomic.h>头文件中。以下是一些常用的原子操作API:

  • 定义原子变量

    1
    atomic_int my_atomic_var;
  • 初始化原子变量

    1
    atomic_init(&my_atomic_var, 0);
  • 读取原子变量的值

    1
    int value = atomic_load(&my_atomic_var);
  • 存储值到原子变量

    1
    atomic_store(&my_atomic_var, 5);
  • 增加原子变量的值

    1
    atomic_fetch_add(&my_atomic_var, 1);
  • 减少原子变量的值

    1
    atomic_fetch_sub(&my_atomic_var, 1);
  • 比较并交换(Compare and Swap, CAS)

    1
    2
    3
    int expected = 0;
    int desired = 1;
    int result = atomic_compare_exchange_strong(&my_atomic_var, &expected, desired);

3-1-3、 C++11 标准库中的原子操作

在C++11标准库中,原子操作的API定义在<atomic>头文件中。以下是一些常用的原子操作API:

  • 定义原子变量

    1
    std::atomic<int> my_atomic_var;
  • 初始化原子变量

    1
    my_atomic_var = 0;
  • 读取原子变量的值

    1
    int value = my_atomic_var.load();
  • 存储值到原子变量

    1
    my_atomic_var.store(5);
  • 增加原子变量的值

    1
    my_atomic_var.fetch_add(1);
  • 减少原子变量的值

    1
    my_atomic_var.fetch_sub(1);
  • 比较并交换(Compare and Swap, CAS)

    1
    2
    3
    int expected = 0;
    int desired = 1;
    bool success = my_atomic_var.compare_exchange_strong(expected, desired);

示例代码

以下是一些使用不同环境中的原子操作的示例代码。

Linux 内核中的原子操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/atomic.h>

static atomic_t my_atomic_var;

static int __init my_module_init(void) {
atomic_set(&my_atomic_var, 0);
printk(KERN_INFO "Module loaded\n");
return 0;
}

static void __exit my_module_exit(void) {
printk(KERN_INFO "Module unloaded\n");
}

static void atomic_operations(void) {
atomic_inc(&my_atomic_var);
int value = atomic_read(&my_atomic_var);
printk(KERN_INFO "Atomic variable value: %d\n", value);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple module with atomic operations");

POSIX 线程中的原子操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdatomic.h>
#include <pthread.h>

atomic_int my_atomic_var;

void* thread_func(void* arg) {
atomic_fetch_add(&my_atomic_var, 1);
int value = atomic_load(&my_atomic_var);
printf("Atomic variable value: %d\n", value);
return NULL;
}

int main() {
atomic_init(&my_atomic_var, 0);
pthread_t threads[5];

for (int i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, thread_func, NULL);
}

for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}

return 0;
}

C++11 标准库中的原子操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>

std::atomic<int> my_atomic_var;

void thread_func() {
my_atomic_var.fetch_add(1);
int value = my_atomic_var.load();
std::cout << "Atomic variable value: " << value << std::endl;
}

int main() {
my_atomic_var = 0;
std::vector<std::thread> threads;

for (int i = 0; i < 5; i++) {
threads.emplace_back(thread_func);
}

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

return 0;
}

总结

原子操作的API在不同的操作系统和编程环境中有所不同。在Linux内核中,原子操作的API定义在<linux/atomic.h>中;在POSIX线程中,原子操作的API定义在<stdatomic.h>中;在C++11标准库中,原子操作的API定义在<atomic>中。选择合适的原子操作API取决于具体的编程环境和需求。原子操作可以显著提高并发程序的性能,特别是在多线程环境中。

4、互斥锁

互斥锁(Mutex)是一种同步机制,用于保护共享资源,确保同一时间只有一个线程可以访问该资源。互斥锁的基本思想是当一个线程试图获取已经被占用的互斥锁时,它会被阻塞,直到锁可用。互斥锁适用于持有时间较长的场景,因为它允许线程在等待锁时进入睡眠状态,从而避免了线程切换的开销。

4-1、api

以下是不同操作系统和编程环境中的互斥锁API示例:

4-1-1、 Linux 内核中的互斥锁

在Linux内核中,互斥锁的API定义在<linux/mutex.h>头文件中。以下是一些常用的互斥锁API:

  • 定义互斥锁

    1
    struct mutex my_mutex;
  • 初始化互斥锁

    1
    mutex_init(&my_mutex);
  • 获取互斥锁

    1
    mutex_lock(&my_mutex);
  • 释放互斥锁

    1
    mutex_unlock(&my_mutex);
  • 尝试获取互斥锁

    1
    2
    3
    4
    5
    if (mutex_trylock(&my_mutex)) {
    // 成功获取锁
    } else {
    // 获取锁失败
    }

4-1-2、 POSIX 线程(Pthreads)中的互斥锁

在POSIX线程中,互斥锁的API定义在<pthread.h>头文件中。以下是一些常用的互斥锁API:

  • 定义互斥锁

    1
    pthread_mutex_t my_mutex;
  • 初始化互斥锁

    1
    pthread_mutex_init(&my_mutex, NULL);
  • 获取互斥锁

    1
    pthread_mutex_lock(&my_mutex);
  • 释放互斥锁

    1
    pthread_mutex_unlock(&my_mutex);
  • 销毁互斥锁

    1
    pthread_mutex_destroy(&my_mutex);
  • 尝试获取互斥锁

    1
    2
    3
    4
    5
    if (pthread_mutex_trylock(&my_mutex) == 0) {
    // 成功获取锁
    } else {
    // 获取锁失败
    }

4-1-3、C++11 标准库中的互斥锁

在C++11标准库中,互斥锁的API定义在<mutex>头文件中。以下是一些常用的互斥锁API:

  • 定义互斥锁

    1
    std::mutex my_mutex;
  • 获取互斥锁

    1
    my_mutex.lock();
  • 释放互斥锁

    1
    my_mutex.unlock();
  • 尝试获取互斥锁

    1
    2
    3
    4
    5
    if (my_mutex.try_lock()) {
    // 成功获取锁
    } else {
    // 获取锁失败
    }
  • 使用锁保护代码块

    1
    2
    std::lock_guard<std::mutex> lock(my_mutex);
    // 临界区代码

示例代码

以下是一些使用不同环境中的互斥锁的示例代码。

Linux 内核中的互斥锁示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mutex.h>

static struct mutex my_mutex;
static int shared_resource = 0;

static int __init my_module_init(void) {
mutex_init(&my_mutex);
printk(KERN_INFO "Module loaded\n");
return 0;
}

static void __exit my_module_exit(void) {
printk(KERN_INFO "Module unloaded\n");
}

static void critical_section(void) {
mutex_lock(&my_mutex);
// 临界区代码
shared_resource++;
printk(KERN_INFO "Shared resource: %d\n", shared_resource);
mutex_unlock(&my_mutex);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple module with mutex");

POSIX 线程中的互斥锁示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t my_mutex;
int shared_resource = 0;

void* thread_func(void* arg) {
pthread_mutex_lock(&my_mutex);
// 临界区代码
shared_resource++;
printf("Shared resource: %d\n", shared_resource);
pthread_mutex_unlock(&my_mutex);
return NULL;
}

int main() {
pthread_mutex_init(&my_mutex, NULL);
pthread_t threads[5];

for (int i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, thread_func, NULL);
}

for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}

pthread_mutex_destroy(&my_mutex);
return 0;
}

C++11 标准库中的互斥锁示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

std::mutex my_mutex;
int shared_resource = 0;

void thread_func() {
std::lock_guard<std::mutex> lock(my_mutex);
// 临界区代码
shared_resource++;
std::cout << "Shared resource: " << shared_resource << std::endl;
}

int main() {
std::vector<std::thread> threads;

for (int i = 0; i < 5; i++) {
threads.emplace_back(thread_func);
}

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

return 0;
}

总结

互斥锁的API在不同的操作系统和编程环境中有所不同。在Linux内核中,互斥锁的API定义在<linux/mutex.h>中;在POSIX线程中,互斥锁的API定义在<pthread.h>中;在C++11标准库中,互斥锁的API定义在<mutex>中。选择合适的互斥锁API取决于具体的编程环境和需求。互斥锁可以有效地保护共享资源,避免并发问题,但在使用时需要注意避免死锁和其他并发问题。

5、信号量

信号量(Semaphore)是一种同步机制,用于控制对共享资源的访问,可以是二进制信号量(类似于互斥锁)或多级信号量。信号量通过维护一个计数器来管理资源的访问权限,允许多个线程或进程同时访问共享资源,但数量受限于信号量的初始值。

5-1、api

以下是不同操作系统和编程环境中的信号量API示例:

5-1-1. Linux 内核中的信号量

在Linux内核中,信号量的API定义在<linux/semaphore.h>头文件中。以下是一些常用的信号量API:

  • 定义信号量

    1
    struct semaphore my_semaphore;
  • 初始化信号量

    1
    sema_init(&my_semaphore, count);
  • 获取信号量

    1
    down(&my_semaphore);
  • 释放信号量

    1
    up(&my_semaphore);
  • 尝试获取信号量

    1
    2
    3
    4
    5
    if (down_trylock(&my_semaphore) == 0) {
    // 成功获取信号量
    } else {
    // 获取信号量失败
    }

5-1-2、 POSIX 线程(Pthreads)中的信号量

在POSIX线程中,信号量的API定义在<semaphore.h>头文件中。以下是一些常用的信号量API:

  • 定义信号量

    1
    sem_t my_semaphore;
  • 初始化信号量

    1
    sem_init(&my_semaphore, 0, count);
  • 获取信号量

    1
    sem_wait(&my_semaphore);
  • 释放信号量

    1
    sem_post(&my_semaphore);
  • 销毁信号量

    1
    sem_destroy(&my_semaphore);
  • 尝试获取信号量

    1
    2
    3
    4
    5
    if (sem_trywait(&my_semaphore) == 0) {
    // 成功获取信号量
    } else {
    // 获取信号量失败
    }

5-1-3、 C++11 标准库中的信号量

C++11标准库中并没有直接提供信号量的实现,但可以使用std::counting_semaphore(C++20引入)或通过std::mutexstd::condition_variable来实现信号量。以下是使用std::counting_semaphore的示例:

  • 定义信号量

    1
    std::counting_semaphore<5> my_semaphore(5); // 初始值为5
  • 获取信号量

    1
    my_semaphore.acquire();
  • 释放信号量

    1
    my_semaphore.release();
  • 尝试获取信号量

    1
    2
    3
    4
    5
    if (my_semaphore.try_acquire()) {
    // 成功获取信号量
    } else {
    // 获取信号量失败
    }

示例代码

以下是一些使用不同环境中的信号量的示例代码。

Linux 内核中的信号量示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/semaphore.h>

static struct semaphore my_semaphore;
static int shared_resource = 0;

static int __init my_module_init(void) {
sema_init(&my_semaphore, 1); // 初始化信号量为1,相当于互斥锁
printk(KERN_INFO "Module loaded\n");
return 0;
}

static void __exit my_module_exit(void) {
printk(KERN_INFO "Module unloaded\n");
}

static void critical_section(void) {
down(&my_semaphore);
// 临界区代码
shared_resource++;
printk(KERN_INFO "Shared resource: %d\n", shared_resource);
up(&my_semaphore);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple module with semaphore");

POSIX 线程中的信号量示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>

sem_t my_semaphore;
int shared_resource = 0;

void* thread_func(void* arg) {
sem_wait(&my_semaphore);
// 临界区代码
shared_resource++;
printf("Shared resource: %d\n", shared_resource);
sem_post(&my_semaphore);
return NULL;
}

int main() {
sem_init(&my_semaphore, 0, 1); // 初始化信号量为1,相当于互斥锁
pthread_t threads[5];

for (int i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, thread_func, NULL);
}

for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}

sem_destroy(&my_semaphore);
return 0;
}

C++20 中的信号量示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <semaphore>
#include <thread>
#include <vector>

std::counting_semaphore<5> my_semaphore(5); // 初始值为5
int shared_resource = 0;

void thread_func() {
my_semaphore.acquire();
// 临界区代码
shared_resource++;
std::cout << "Shared resource: " << shared_resource << std::endl;
my_semaphore.release();
}

int main() {
std::vector<std::thread> threads;

for (int i = 0; i < 5; i++) {
threads.emplace_back(thread_func);
}

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

return 0;
}

总结

信号量的API在不同的操作系统和编程环境中有所不同。在Linux内核中,信号量的API定义在<linux/semaphore.h>中;在POSIX线程中,信号量的API定义在<semaphore.h>中;在C++20标准库中,信号量的API定义在<semaphore>中。选择合适的信号量API取决于具体的编程环境和需求。信号量可以有效地控制对共享资源的访问,允许多个线程或进程同时访问,但数量受限于信号量的初始值。