linux的线程同步(条件变量和锁)

目录

一、前言

[二、互斥锁(Mutex Lock)](#二、互斥锁(Mutex Lock))

1.互斥锁的原理

[2.代码示例(使用 C 语言和 pthread 库)](#2.代码示例(使用 C 语言和 pthread 库))

三、条件变量

1.条件变量的原理

[2.代码示例(使用 C 语言和 pthread 库)](#2.代码示例(使用 C 语言和 pthread 库))

[四、自旋锁(Spin Lock)](#四、自旋锁(Spin Lock))

1.自旋锁原理

[2.代码示例(使用 C 语言)](#2.代码示例(使用 C 语言))

[五、读写锁(Read - Write Lock)](#五、读写锁(Read - Write Lock))

1.读写锁原理

[2.代码示例(使用 C 语言和 pthread 库)](#2.代码示例(使用 C 语言和 pthread 库))

六、总结


一、前言

在Linux系统中,多线程编程是实现高效并发的重要手段。线程同步机制是确保线程间协调工作、避免资源竞争的关键技术。互斥锁、条件变量、自旋锁以及读写锁是常见的线程同步工具,它们在不同场景下发挥着重要作用。

二、互斥锁(Mutex Lock)

1.互斥锁的原理

互斥锁用于保护共享资源,确保在任何时刻只有一个线程可以访问被保护的资源。当一个线程获取了互斥锁后,其他试图获取该锁的线程将被阻塞,直到锁被释放。这种机制通过互斥的方式来避免多个线程同时访问和修改共享数据,从而防止数据竞争和不一致性。

2.代码示例(使用 C 语言和 pthread 库)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 定义互斥锁
pthread_mutex_t mutex;
// 共享变量
int shared_variable = 0;

// 线程函数
void *thread_function(void *arg) {
    // 加锁
    pthread_mutex_lock(&mutex);
    // 对共享变量进行操作
    shared_variable++;
    printf("Thread ID: %lu, Shared variable value: %d\n", pthread_self(), shared_variable);
    // 解锁
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[5];
    // 初始化互斥锁
    if (pthread_mutex_init(&mutex, NULL)!= 0) {
        perror("Mutex initialization failed");
        return 1;
    }
    for (int i = 0; i < 5; ++i) {
        // 创建线程
        if (pthread_create(&threads[i], NULL, thread_function, NULL)!= 0) {
            perror("Thread creation failed");
            return 1;
        }
    }
    for (int i = 0; i < 5; ++i) {
        // 等待线程结束
        if (pthread_join(threads[i], NULL)!= 0) {
            perror("Thread join failed");
            return 1;
        }
    }
    // 销毁互斥锁
    if (pthread_mutex_destroy(&mutex)!= 0) {
        perror("Mutex destruction failed");
        return 1;
    }
    return 0;
}
  • 分析
    • 首先,通过pthread_mutex_init函数初始化互斥锁mutex。这个函数在成功时返回 0,否则返回一个错误码。
    • 在thread_function函数中,每个线程首先调用pthread_mutex_lock来获取互斥锁。如果此时锁已经被其他线程持有,调用线程会被阻塞在这里,直到锁被释放。
    • 当线程成功获取锁后,它对共享变量shared_variable进行自增操作,并打印出线程 ID 和共享变量的值。然后,通过pthread_mutex_unlock释放互斥锁,使得其他线程有机会获取锁并访问共享资源。
    • main函数中,创建了 5 个线程,并使用pthread_join等待每个线程结束。最后,通过pthread_mutex_destroy销毁互斥锁。

三、条件变量

1.条件变量的原理

条件变量通常与互斥锁一起使用,用于线程之间的同步。它允许一个线程等待某个条件为真,而另一个线程可以在条件满足时通知等待的线程。这样可以避免线程在不满足条件的情况下一直占用 CPU 资源进行无效的检查。

2.代码示例(使用 C 语言和 pthread 库)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 定义互斥锁和条件变量
pthread_mutex_t mutex;
pthread_cond_t cond;
// 共享变量
int shared_variable = 0;

// 生产者线程函数
void *producer_thread(void *arg) {
    pthread_mutex_lock(&mutex);
    // 生产数据,修改共享变量
    shared_variable++;
    // 发送信号给等待的消费者线程
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

// 消费者线程函数
void *consumer_thread(void *arg) {
    pthread_mutex_lock(&mutex);
    // 检查条件是否满足,如果不满足则等待
    while (shared_variable == 0) {
        pthread_cond_wait(&cond, &mutex);
    }
    // 消费数据,访问共享变量
    printf("Consumer: Shared variable value: %d\n", shared_variable);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

int main() {
    pthread_t producer, consumer;
    // 初始化互斥锁和条件变量
    if (pthread_mutex_init(&mutex, NULL)!= 0) {
        perror("Mutex initialization failed");
        return 1;
    }
    if (pthread_cond_init(&cond, NULL)!= 0) {
        perror("Condition variable initialization failed");
        return 1;
    }
    // 创建生产者线程
    if (pthread_create(&producer, NULL, producer_thread, NULL)!= 0) {
        perror("Producer thread creation failed");
        return 1;
    }
    // 创建消费者线程
    if (pthread_create(&consumer, NULL, consumer_thread, NULL)!= 0) {
        perror("Consumer thread creation failed");
        return 1;
    }
    // 等待生产者和消费者线程结束
    if (pthread_join(producer, NULL)!= 0) {
        perror("Producer thread join failed");
        return 1;
    }
    if (pthread_join(consumer, NULL)!= 0) {
        perror("Consumer thread join failed");
        return 1;
    }
    // 销毁互斥锁和条件变量
    if (pthread_mutex_destroy(&mutex)!= 0) {
        perror("Mutex destruction failed");
        return 1;
    }
    if (pthread_cond_destroy(&cond)!= 0) {
        perror("Condition variable destruction failed");
        return 1;
    }
    return 0;
}
  • 分析
    • main函数中,首先通过pthread_mutex_init和pthread_cond_init分别初始化互斥锁mutex和条件变量cond
    • 生产者线程函数producer_thread获取互斥锁,修改共享变量shared_variable,然后通过pthread_cond_signal发送信号,表示共享变量已经被修改,可能满足消费者线程等待的条件。最后释放互斥锁。
    • 消费者线程函数consumer_thread获取互斥锁后,检查共享变量是否满足条件(这里是shared_variable是否大于 0)。如果不满足条件,就调用pthread_cond_wait函数等待。这个函数会自动释放互斥锁,让其他线程(如生产者线程)能够获取锁并修改共享变量。当收到信号并且条件满足时,pthread_cond_wait会重新获取互斥锁,然后消费者线程继续执行,访问共享变量并打印相关信息,最后释放互斥锁。
    • main函数最后,销毁互斥锁和条件变量。

四、自旋锁(Spin Lock)

1.自旋锁原理

自旋锁也是一种用于保护共享资源的同步机制。当一个线程试图获取自旋锁而锁已经被占用时,它不会像互斥锁那样阻塞自己,而是会一直 "自旋",即不断地检查锁是否已经被释放。这种方式在等待时间较短的情况下可能比互斥锁更高效,因为它避免了线程切换的开销。但是,如果等待时间过长,自旋锁会占用大量的 CPU 资源,因为线程一直在循环检查。

2.代码示例(使用 C 语言)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdbool.h>

// 定义自旋锁结构体
typedef struct {
    volatile bool locked;
} spinlock_t;

// 初始化自旋锁
void spinlock_init(spinlock_t *lock) {
    lock->locked = false;
}

// 获取自旋锁
void spinlock_lock(spinlock_t *lock) {
    while (__sync_lock_test_and_set(&lock->locked, true)) {
        // 自旋等待
    }
}

// 释放自旋锁
void spinlock_unlock(spinlock_t *lock) {
    __sync_lock_release(&lock->locked);
}

// 共享变量
int shared_variable = 0;
spinlock_t spinlock;

// 线程函数
void *thread_function(void *arg) {
    spinlock_lock(&spinlock);
    shared_variable++;
    printf("Thread ID: %lu, Shared variable value: %d\n", pthread_self(), shared_variable);
    spinlock_unlock(&spinlock);
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[5];
    spinlock_init(&spinlock);
    for (int i = 0; i < 5; ++i) {
        if (pthread_create(&threads[i], NULL, thread_function, NULL)!= 0) {
            perror("Thread creation failed");
            return 1;
        }
    }
    for (int i = 0; i < 5; ++i) {
        if (pthread_join(threads[i], NULL)!= 0) {
            perror("Thread join failed");
            return 1;
        }
    }
    return 0;
}
  • 分析
    • 首先定义了自旋锁结构体spinlock_t,其中包含一个volatile bool类型的变量locked用于表示锁的状态。volatile关键字用于告诉编译器这个变量可能会被外部因素(如其他线程)改变,不要对其进行优化。
    • spinlock_init函数用于初始化自旋锁,将locked设置为false,表示锁未被占用。
    • spinlock_lock函数通过__sync_lock_test_and_set原子操作来尝试获取自旋锁。这个原子操作会检查locked的值,如果为false,则将其设置为true并返回旧值(此时获取锁成功);如果为true,则返回true,表示锁已经被占用,线程会进入循环等待(自旋),直到获取到锁。
    • 在thread_function函数中,线程首先获取自旋锁,然后对共享变量shared_variable进行操作,打印相关信息,最后释放自旋锁。
    • 在main函数中,创建了多个线程并等待它们结束。

五、读写锁(Read - Write Lock)

1.读写锁原理

读写锁用于区分对共享资源的读操作和写操作。它允许多个线程同时对共享资源进行读操作,但在有线程进行写操作时,其他线程(无论是读还是写)都需要等待。这种机制适用于共享资源读操作频繁而写操作相对较少的场景,可以提高并发性能。

2.代码示例(使用 C 语言和 pthread 库)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

// 定义读写锁
pthread_rwlock_t rwlock;
// 共享变量
int shared_variable = 0;

// 读线程函数
void *read_thread(void *arg) {
    // 获取读锁
    pthread_rwlock_rdlock(&rwlock);
    printf("Read thread ID: %lu, Shared variable value: %d\n", pthread_self(), shared_variable);
    // 释放读锁
    pthread_rwlock_unlock(&rwlock);
    pthread_exit(NULL);
}

// 写线程函数
void *write_thread(void *arg) {
    // 获取写锁
    pthread_rwlock_wrlock(&rwlock);
    shared_variable++;
    printf("Write thread ID: %lu, Shared variable value: %d\n", pthread_self(), shared_variable);
    // 释放写锁
    pthread_rwlock_unlock(&rwlock);
    pthread_exit(NULL);
}

int main() {
    pthread_t read_threads[5];
    pthread_t write_thread;
    // 初始化读写锁
    if (pthread_rwlock_init(&rwlock, NULL)!= 0) {
        perror("Read - write lock initialization failed");
        return 1;
    }
    for (int i = 0; i < 5; ++i) {
        // 创建读线程
        if (pthread_create(&read_threads[i], NULL, read_thread, NULL)!= 0) {
            perror("Read thread creation failed");
            return 1;
        }
    }
    // 创建写线程
    if (pthread_create(&write_thread, NULL, write_thread, NULL)!= 0) {
        perror("Write thread creation failed");
        return 1;
    }
    for (int i = 0; i < 5; ++i) {
        // 等待读线程结束
        if (pthread_join(read_threads[i], NULL)!= 0) {
            perror("Read thread join failed");
            return 1;
        }
    }
    if (pthread_join(write_thread, NULL)!= 0) {
        perror("Write thread join failed");
        return 1;
    }
    // 销毁读写锁
    if (pthread_rwlock_destroy(&rwlock)!= 0) {
        perror("Read - write lock destruction failed");
        return 1;
    }
    return 0;
}
  • 分析
    • main函数中,首先通过pthread_rwlock_init函数初始化读写锁rwlock。
    • 读线程函数read_thread通过pthread_rwlock_rdlock获取读锁,这样多个读线程可以同时获取读锁并访问共享变量shared_variable。读取完成后,通过pthread_rwlock_unlock释放读锁。
    • 写线程函数write_thread通过pthread_rwlock_wrlock获取写锁。当一个线程获取写锁后,其他线程(无论是读还是写)都无法获取锁,直到写操作完成。写线程对共享变量进行自增操作,打印相关信息,然后释放写锁。
    • main函数最后,等待所有线程结束并销毁读写锁。

六、总结

互斥锁、条件变量、自旋锁和读写锁在Linux线程同步中各有特点和适用场景。互斥锁是最常用的同步工具,适用于大多数场景。条件变量用于在特定条件满足时通知线程。自旋锁适用于对锁的等待时间较短且不希望线程上下文切换的场景。读写锁则在对共享资源进行读和写操作时提高效率。

相关推荐
DS小龙哥1 小时前
基于物联网的冻保鲜运输智能控制系统
物联网
只做开心事1 小时前
Linux之信号量
linux
Xi_er_3 小时前
密钥管理系统在数据安全解决方案中的重要性
运维·数据仓库·物联网·web安全·前端框架·智慧城市·安全架构
hqxnb6664 小时前
深入理解 Linux 管道:创建与应用详解(匿名管道&&进程池)
linux·运维·服务器
Run Out Of Brain5 小时前
使用Oracle的Debian软件包在Linux上安装MySQL
linux·oracle·debian
等一场春雨5 小时前
Windows 11 上通过 WSL (Windows Subsystem for Linux) 安装 MySQL 8
linux·windows·mysql
加勒比之杰克5 小时前
【数据库初阶】MySQL数据类型
linux·数据库·mysql·数据类型·varchar
vvw&5 小时前
如何在 Ubuntu 24.04 上安装 Drupal CMS 11 并配置 Nginx, MariaDB 和 SSL 教程
linux·运维·服务器·nginx·ubuntu·ssl·mariadb
未完成的歌~5 小时前
Kali 离线安装 ipmitool 笔记
linux·运维·笔记
vvw&5 小时前
在 Ubuntu 22.04 上部署 AppArmor 应用安全教程
linux·运维·服务器·nginx·安全·ubuntu·node.js