在多线程编程中,线程间竞争与同步是至关重要的概念。正确使用同步机制可以有效避免数据不一致、脏数据等问题。本文将详细介绍线程间竞争的基本概念、互斥量、信号量、死锁以及条件变量,并讲解主要使用的函数,附带代码示例和注释。
一、基本概念
- 原子操作:中途不会被打断的操作称为原子操作,确保不会被其他线程影响。
- 竞争与同步:同一进程中的线程共享大多数资源,竞争可能导致数据损坏或不一致。通过同步机制,线程可以相互协调,避免出现这种情况。
- 临界区与临界资源:
- 临界区:多个线程同时访问的代码块。
- 临界资源:被多个线程同时访问的数据。
二、互斥量(互斥锁)
互斥量是保护临界区的常用同步机制。以下是使用 pthread
库的互斥量相关函数及其讲解:
-
pthread_mutex_t
:互斥量的数据类型,用于定义互斥量变量。 -
pthread_mutex_init
:- 原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- 功能:初始化一个互斥量。通常将
attr
设为NULL
。
- 原型:
-
pthread_mutex_lock
:- 原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 功能:对互斥量进行加锁。成功加锁后继续执行,失败则阻塞,直到互斥量解锁。
- 原型:
-
pthread_mutex_trylock
:- 原型:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 功能:尝试对互斥量加锁。成功返回 0,失败则立即返回
EBUSY
。
- 原型:
-
pthread_mutex_unlock
:- 原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 功能:解锁互斥量。
- 原型:
-
pthread_mutex_destroy
:- 原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 功能:销毁互斥量,释放相关资源。
- 原型:
示例代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex; // 定义互斥量
void* thread_function(void* arg) {
pthread_mutex_lock(&mutex); // 加锁
// 临界区代码
printf("Thread %ld is in the critical section.\n", (long)arg);
pthread_mutex_unlock(&mutex); // 解锁
return NULL;
}
int main() {
pthread_t threads[5];
pthread_mutex_init(&mutex, NULL); // 初始化互斥量
for (long i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, thread_function, (void*)i);
}
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL); // 等待线程结束
}
pthread_mutex_destroy(&mutex); // 销毁互斥量
return 0;
}
三、信号量
信号量用于控制访问有限资源的线程数量。下面是信号量的使用示例及主要函数讲解:
-
sem_t
:信号量的数据类型。 -
sem_init
:- 原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 功能:初始化信号量。
pshared
为 0 时表示信号量只能在本进程内使用,value
是信号量的初始值。
- 原型:
-
sem_wait
:- 原型:
int sem_wait(sem_t *sem);
- 功能:对信号量进行减1操作。如果信号量为0则阻塞,直到信号量大于0。
- 原型:
-
sem_trywait
:- 原型:
int sem_trywait(sem_t *sem);
- 功能:尝试对信号量减1操作。成功返回 0,失败返回
EAGAIN
。
- 原型:
-
sem_post
:- 原型:
int sem_post(sem_t *sem);
- 功能:对信号量进行加1操作。
- 原型:
-
sem_destroy
:- 原型:
int sem_destroy(sem_t *sem);
- 功能:销毁信号量,释放资源。
- 原型:
示例代码
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
sem_t semaphore; // 定义信号量
void* thread_function(void* arg) {
sem_wait(&semaphore); // P操作,减1
// 临界区代码
printf("Thread %ld is in the critical section.\n", (long)arg);
sem_post(&semaphore); // V操作,加1
return NULL;
}
int main() {
pthread_t threads[5];
sem_init(&semaphore, 0, 2); // 初始化信号量,初始值为2
for (long i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, thread_function, (void*)i);
}
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}
sem_destroy(&semaphore); // 销毁信号量
return 0;
}
四、死锁
1. 什么是死锁
死锁是指多个进程或线程互相等待对方释放资源,导致所有进程或线程都无法继续执行。
2. 死锁的四大必要条件
- 资源互斥:资源只能被一个进程或线程占用。
- 占有且请求:已占有的资源可继续请求新的资源。
- 资源不可剥夺:已占有的资源不能被强制获取。
- 环路等待:形成等待环路。
3. 如何防止死锁
- 破坏资源互斥:允许资源共享(但难以实现)。
- 破坏占有且请求:采用预分配资源的方式。
- 破坏资源不可剥夺:允许进程释放资源后再请求。
- 破坏等待环路:按顺序请求资源。
五、条件变量
条件变量用于线程间的协作,可以让线程在特定条件下进入睡眠或唤醒。以下是条件变量的主要函数及讲解:
-
pthread_cond_t
:条件变量的数据类型。 -
pthread_cond_init
:- 原型:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
- 功能:初始化条件变量。通常将
attr
设为NULL
。
- 原型:
-
pthread_cond_wait
:- 原型:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 功能:让当前线程进入睡眠状态并释放互斥锁,直到被唤醒。
- 原型:
-
pthread_cond_signal
:- 原型:
int pthread_cond_signal(pthread_cond_t *cond);
- 功能:唤醒一个在条件变量上等待的线程。
- 原型:
-
pthread_cond_broadcast
:- 原型:
int pthread_cond_broadcast(pthread_cond_t *cond);
- 功能:唤醒所有在条件变量上等待的线程。
- 原型:
-
pthread_cond_destroy
:- 原型:
int pthread_cond_destroy(pthread_cond_t *cond);
- 功能:销毁条件变量,释放资源。
- 原型:
示例代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex;
pthread_cond_t cond; // 定义条件变量
int ready = 0; // 共享变量
void* producer(void* arg) {
pthread_mutex_lock(&mutex);
ready = 1; // 生产数据
pthread_cond_signal(&cond); // 唤醒消费者
pthread_mutex_unlock(&mutex);
return NULL;
}
void* consumer(void* arg) {
pthread_mutex_lock(&mutex);
while (ready == 0) { // 检查条件
pthread_cond_wait(&cond, &mutex); // 等待条件满足
}
printf("Consumer consumed the data.\n");
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t prod, cons;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_create(&prod, NULL, producer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
总结
线程间的竞争与同步是多线程编程的重要内容。通过理解互斥量、信号量、死锁以及条件变量的使用,以及相关函数的详细讲解,可以有效地实现线程间的协调与合作。希望本篇博客能够帮助你更好地理解这些概念并在实际编程中应用。