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

相关推荐
Raners_3 分钟前
【Linux】文件权限以及特殊权限(SUID、SGID)
linux·安全
egoist20236 分钟前
【Linux仓库】进程优先级及进程调度【进程·肆】
linux·运维·服务器·进程切换·进程调度·进程优先级·大o1调度
2301_1472583691 小时前
7月2日作业
java·linux·服务器
xuanzdhc6 小时前
Linux 基础IO
linux·运维·服务器
愚润求学6 小时前
【Linux】网络基础
linux·运维·网络
bantinghy6 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志7 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手7 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
小米里的大麦9 天前
014 Linux 2.6内核进程调度队列(了解)
linux·运维·驱动开发