Linux线程 分离和同步与互斥 条件变量
- 1. 分离线程
- 2. 线程互斥与互斥量
- 3. 线程同步与竞态条件
- 4. pthread库与条件变量
- 5. 生产者-消费者
1. 分离线程
-
什么是线程分离?
线程分离是指线程在结束时,操作系统会自动回收其资源,而无需其他线程显式地等待它的结束或调用pthread_join函数。这种机制允许主线程不必关心子线程的状态,从而提高程序的并发性和可维护性。 -
pthread_detach函数
pthread_detach 函数是一个用于将线程设置为分离状态的POSIX线程库函数。通过调用这个函数,线程的资源将在其终止时自动回收,无需其他线程调用 pthread_join 来等待该线程的结束。
#include <pthread.h>
int pthread_detach(pthread_t thread);
参数说明
thread:要设置为分离状态的线程标识符。
返回值
成功:返回0。
失败:返回错误码,可通过 perror 函数打印出错误信息。
代码示例:
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 3
// 线程执行的任务
void *thread_function(void *arg) {
int thread_id = *((int *)arg);
int n = 5;
// 模拟线程执行任务,循环5次
while (n--) {
printf("线程 %d 正在运行\n", thread_id);
sleep(1); // 每秒打印一次,模拟线程执行任务
}
// 线程结束
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int thread_ids[NUM_THREADS];
// 创建并启动3个线程
for (int i = 0; i < NUM_THREADS; ++i) {
thread_ids[i] = i + 1;
// 创建线程
if (pthread_create(&threads[i], NULL, thread_function, (void *)&thread_ids[i]) != 0) {
perror("创建线程失败");
return -1;
}
// 设置线程为分离状态
if (pthread_detach(threads[i]) != 0) {
perror("设置线程为分离状态失败");
return -1;
}
}
// 主线程执行的任务
while (1) {
printf("主线程正在运行\n");
sleep(1); // 主线程每秒打印一次,模拟执行其他任务
}
return 0;
}
运行时,使用 while :; do ps -aL ;sleep 1;done
实时查看。
这个程序创建了3个线程,每个线程模拟执行任务5次。主线程在一个无限循环中每秒打印一次消息,模拟执行其他任务。这里使用了 pthread_create 来创建线程,pthread_detach 将线程设置为分离状态,确保线程在结束时能够自动释放资源。
2. 线程互斥与互斥量
多线程编程是一种广泛应用于提高程序性能的技术。然而,当多个线程同时访问和修改共享资源时,可能会导致数据竞争(Data Race)和其他并发问题。为了解决这些问题,互斥量(Mutex)被引入,成为多线程编程中的重要工具。
-
为什么需要互斥量?
在多线程环境下,多个线程可能同时访问和修改共享资源,导致竞态条件。竞态条件是一种并发问题,可能引起不确定的行为。为了解决这些问题,我们引入互斥量来保护共享资源,确保一次只有一个线程能够访问它。 -
临界资源与临界区
临界资源: 多个线程执行流共享的资源称为临界资源,它可能是一块内存、文件、网络连接等。对这些资源的并发访问可能导致不确定的结果或数据损坏。
临界区: 是每个线程内部访问临界资源的代码段。在临界区内,对共享资源的访问需要进行互斥,以确保同一时刻只有一个线程能够进入临界区。 -
互斥与原子性
互斥: 互斥是一种机制,确保在任何时刻只有一个线程能够进入临界区,从而访问临界资源,通常对临界资源起保护作用。
原子性: 表示一个操作是不可中断的,要么全部执行成功,要么完全不执行。在多线程环境中,原子操作是不会被其他线程中断的操作,确保原子性可以避免竞态条件和数据不一致性。
示例程序说明(不使用互斥量):
上述程序输出结果表明,由于多个线程同时访问和修改共享资源(available_tickets)而没有同步机制,导致竞态条件的发生。在输出中,票数递减,并最终变成负数。由于线程执行的顺序不确定,每次运行结果可能不同。
- 互斥量和加锁
- pthread_mutex_t 类型
pthread_mutex_t 是 POSIX 线程库提供的互斥量类型,用于在多线程环境中同步对共享资源的访问。它是一个结构体类型,通常通过指针进行操作 - pthread_mutex_init 函数
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
功能: 初始化互斥量对象。
参数:
mutex 是指向要初始化的互斥量对象的指针。
attr 是指向互斥量属性对象的指针。可以为 NULL,表示使用默认属性。
返回值: 若成功,返回 0;否则,返回错误码
- pthread_mutex_destroy 函数
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能: 销毁互斥量对象。
参数: mutex 是指向要销毁的互斥量对象的指针。
返回值: 若成功,返回 0;否则,返回错误码。
- PTHREAD_MUTEX_INITIALIZER 宏
PTHREAD_MUTEX_INITIALIZER 是一个宏,用于静态初始化一个互斥锁(mutex)。
在定义互斥量时,可以使用以下宏进行初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
功能: 使用宏 PTHREAD_MUTEX_INITIALIZER 初始化互斥量对象。
注意: 此方法只能在定义互斥量时使用,不能在运行时再次初始化。
- pthread_mutex_lock 函数
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能: pthread_mutex_lock 的功能是加锁,而且如果互斥量已经被锁定,再次调用 pthread_mutex_lock 将会导致调用线程被阻塞,直到互斥量变为可用。
参数: mutex 是指向要加锁的互斥量对象的指针。
返回值: 若成功,返回 0;否则,返回错误码。
- pthread_mutex_unlock 函数
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能: 解锁。释放互斥量,使其变为可用。
参数: mutex 是指向要解锁的互斥量对象的指针。
返回值: 若成功,返回 0;否则,返回错误码。
示例程序说明(使用互斥量):
pthread_mutex_t mutex 主要用于保护临界区,对共享资源进行读取或修改的那部分代码。在多线程环境中,当多个线程同时访问共享资源而缺乏适当的同步机制时,可能导致数据不一致或产生竞争条件。
通过调用 pthread_mutex_lock 和 pthread_mutex_unlock
函数,程序可以执行互斥操作。在进入临界区前,线程通过加锁确保只有一个线程能够执行临界区代码。而在退出临界区后,通过解锁释放互斥锁,使其他线程有机会进入。
以上程序通过互斥量确保了多线程环境下对共享资源(票数)的安全访问,实现了线程安全的抢票操作。
3. 线程同步与竞态条件
在多线程编程中,多个线程共享进程的资源,这样就可能导致竞态条件的产生。竞态条件是由于多个线程对共享资源的访问顺序不确定而引起的问题。为了解决这一问题,引入了线程同步的概念。
程序示例(不使用线程同步)
从这个输出可以看出,线程2连续抢到了多张票,而其他线程并没有执行抢票的机会。这是因为在没有使用线程同步的情况下,多个线程同时访问了共享资源,导致了竞态条件。
4. pthread库与条件变量
在多线程编程中,线程之间的协调与同步是至关重要的。pthread库提供了一系列工具,其中条件变量(pthread_cond_t)是一种强大的机制。
-
pthread_cond_t类型
pthread_cond_t是pthread库中用于线程同步的一种机制,也被称为条件变量。条件变量允许线程在等待某个条件满足时进入休眠状态,并在条件发生变化时被唤醒。条件变量通常与互斥锁一起使用,以确保多个线程对共享资源的安全访问。 -
pthread_cond_init函数
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
功能:
pthread_cond_init 函数用于初始化条件变量,为其分配必要的资源。参数:
cond:指向要初始化的条件变量的指针。
attr:条件变量的属性,通常设置为 NULL 表示使用默认属性。
返回值:
若成功初始化条件变量,返回 0;否则,返回错误码。
- pthread_cond_destroy 函数
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
pthread_cond_destroy 函数用于销毁条件变量,释放相关资源。参数:
cond:指向要销毁的条件变量的指针。
返回值:
若成功销毁条件变量,返回 0;否则,返回错误码。
- PTHREAD_MUTEX_INITIALIZER 宏
PTHREAD_COND_INITIALIZER宏,是一种静态初始化pthread_cond_t类型的条件变量的方式。
在定义条件变量时,可以使用以下宏进行初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
- pthread_cond_wait函数
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
功能:
pthread_cond_wait 函数用于在等待条件变量的同时暂时释放互斥锁,使线程进入等待状态。参数:
cond:指向要等待的条件变量的指针。
mutex:指向互斥锁的指针,用于保护对条件变量的访问。
返回值:
若成功,返回 0;否则,返回错误码。
- pthread_cond_broadcast函数
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
pthread_cond_broadcast 函数用于向所有等待在条件变量上的线程发送信号,使它们从等待状态中唤醒。参数:
cond:指向要发送信号的条件变量的指针。
返回值:
若成功,返回 0;否则,返回错误码。
- pthread_cond_signal函数
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
功能:
pthread_cond_signal 函数用于向等待在条件变量上的一个线程发送信号,使其从等待状态中唤醒。参数:
cond:指向要发送信号的条件变量的指针。
返回值:
若成功,返回 0;否则,返回错误码。
程序示例(使用线程同步)
pthread_cond_init(&cond, NULL); 用于初始化条件变量 cond,与互斥锁一同使用,用于线程等待和唤醒。在线程执行函数中,通过 pthread_cond_wait(&cond, &mutex); 进行等待,释放互斥锁 mutex 进入阻塞状态。
主线程通过 pthread_cond_signal(&cond); 发送信号,唤醒一个等待条件变量的线程,被唤醒的线程在获取互斥锁之前不能继续执行。这确保了每个线程在唤醒后都能够争夺到互斥锁,避免了线程饥饿的情况。
5. 生产者-消费者
- 生产者消费者模型的概念
生产者消费者模型通过中间容器(阻塞队列)解决了生产者和消费者强耦合的问题。两者不直接通讯,而是通过阻塞队列传递数据。生产者产生数据后,无需等待消费者处理,直接放入阻塞队列。消费者不主动请求数据,而是直接从阻塞队列取出,阻塞队列充当了缓冲区的角色。
- 生产者、消费者之间的关系
在生产者消费者模型中存在三种关系:
生产者和生产者之间的互斥关系: 确保不同生产者之间的操作互斥,以保证数据的正确生成。
消费者和消费者之间的互斥关系: 确保不同消费者之间的操作互斥,防止并发访问导致问题。
生产者和消费者之间的互斥与同步关系: 实现互斥以保证读写安全,同时在缓冲区数据满或空时,确保能够互相等待和通知,实现同步操作。生产者线程和消费者线程是两种不同的角色,彼此之间通过缓冲区进行数据交流,这个缓冲区可被看作是一个特定结构的交换平台。
代码示例:
pthread_mutex_t mutex:互斥锁
作用: 保护对缓冲区的访问,确保在同一时刻只有一个线程能够访问或修改共享资源。
使用场景: 在生产者和消费者访问缓冲区时,通过互斥锁进行加锁和解锁,避免多个线程同时修改共享资源导致数据不一致或冲突。
pthread_cond_t not_full:缓冲区不满的条件变量
作用: 用于通知生产者线程缓冲区不再是满的状态,可以继续生产数据。
使用场景: 当缓冲区满时,生产者线程通过等待该条件变量进入阻塞状态,直到消费者线程通知它缓冲区不再是满的。
pthread_cond_t not_empty:缓冲区不空的条件变量
作用: 用于通知消费者线程缓冲区不再是空的状态,可以继续消费数据。
使用场景: 当缓冲区为空时,消费者线程通过等待该条件变量进入阻塞状态,直到生产者线程通知它缓冲区不再是空的。
这个程序展示了基本的生产者-消费者模型,通过互斥锁和条件变量确保线程安全的访问共享资源。生产者不断生成随机数作为数据项并放入缓冲区,而消费者则从缓冲区中取出数据项进行消费。这种模型确保了在多线程环境中的安全数据传递和协同工作。