POSIX多线程笔记(4):互斥量与条件变量

本文介绍了POSIX多线程编程中的互斥量和条件变量。互斥量用于保证对变量操作的原子性,防止并发访问导致的数据不一致;条件变量则提供了一种线程间同步机制,允许线程等待特定条件满足后再继续执行。内容涵盖互斥量的创建、销毁、锁定与解锁,以及条件变量的创建、等待与通知等概念,并通过实例展示了它们的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

互斥量

互斥量的概念

由于在大多数机器中,对变量的增量和减量操作都不是原子的。比如通常情况下,增量操作包括三个步骤:将内存中的数值装载到CPU寄存器中,将寄存器的值加1,将寄存器中的值写回内存。而机器并不保证在这三步之间不会发生调度,这就导致对变量的增量和减量操作有可能得不到期望的结果。比如,当某线程正在进行增量操作,寄存器中的数值被加1之后发生了线程切换,另一个线程对同一个变量进行了增量操作后又切换回原线程,原线程将寄存器中的值写回内存。此时,变量只被增加了1,而不是增加了2。 要解决这个问题,就要保证对变量的增量和减量操作是原子的,也就是说,在操作过程中不允许发生线程切换。这就用到了互斥量。
互斥量是一种特殊的变量,它可以处于锁定状态(locked),也可以处于解锁状态(unlocked)状态。如果互斥量是锁定的,那么必然有一个线程持有或拥有这个互斥量。如果没有任何一个线程持有这个互斥量,那么这个互斥量就处于解锁、空闲或可用状态。当互斥量空闲,并且有一个线程试图获取这个互斥量时,这个线程就可以获得这个互斥量而不会被阻塞。如果互斥量处于锁定状态,那么试图获取这个互斥量的线程将被阻塞,并加入到这个互斥量的等待队列中。等待队列中的线程获得互斥量的顺序由实现系统决定。这样的机制解决了共享资源的互斥(Mutual Exclusive)访问问题。

创建并初始化一个互斥量

POSIX使用pthread_mutex_t类型的变量来表示互斥量。程序在使用pthread_mutex_t变量之前,必须对其进行初始化。对于静态分配的pthread_mutex_t变量,只要将PTHREAD_MUTEX_INITIALIZER赋给这个变量即可,pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
对于动态创建或不使用默认属性的互斥量来说,就要调用pthread_mutex_init函数来对其进行初始化。函数形式为:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);  

参数mutex是指向要初始化的互斥量的指针,参数attr是设定互斥量属性的变量的指针,如果为NULL,则使互斥量拥有默认属性。

销毁一个互斥量

当不再使用已经定义了的互斥量时,需要将互斥量销毁。函数pthread_mutex_destory用于销毁互斥量。它的形式为:

int pthread_mutex_destroy(pthread_mutex_t *mutex); 

参数mutex指向要销毁的互斥量。如果成功,函数返回0,如果不成功,函数返回一个非零的错误码。

可以用pthread_mutex_init重新初始化被销毁的互斥量。

互斥量的锁定和解锁

POSIX中有两个可以用来获取互斥量的函数,pthread_mutex_lock和pthread_mutex_trylock。pthread_mutex_lock函数会使调用这个函数的线程一直阻塞到互斥量可用为止,而pthread_mutex_trylock会立即返回,如果互斥量空闲,那么调用这个函数的线程将获得互斥量,否则函数将返回EBUSY。pthread_mutex_unlock用于释放互斥量。这三个函数的形式为:

 int pthread_mutex_lock(pthread_mutex_t *mutex); 
 int pthread_mutex_trylock(pthread_mutex_t *mutex); 
 int pthread_mutex_unlock(pthread_mutex_t *mutex); 

如果成功,这些函数就返回0,如果不成功,这些函数就返回一个非零的错误码。

互斥量应用实例

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>
#include <dirent.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <stdio.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void * thread_child1(void *arg)
{
        int i;
        pthread_mutex_lock(&lock);
        for(i=0;i<5;i++)
        {
                usleep(500*1000);
                printf("thread_child1 running!\n");
        }
        pthread_mutex_unlock(&lock);
}
void * thread_child2(void *arg)
{
        int i;
        pthread_mutex_lock(&lock);
        for(i=0;i<5;i++)
        {
                usleep(500*1000);
                printf("thread_child2 running!\n");
        }
        pthread_mutex_unlock(&lock);
}
int main(int argc,char** argv)
{
        pthread_t tid1,tid2;
        printf("create thread_child1!\n");
        pthread_create(&tid1, NULL, (void*)thread_child1, NULL);
        usleep(500*1000);
        printf("create thread_child2!\n");
        pthread_create(&tid2, NULL, (void*)thread_child2, NULL);
        pthread_join(tid2,NULL);
        printf("Leave main thread!\n");
        return 0;
}

运行结果:
在这里插入图片描述

条件变量

条件变量的概念

条件变量是用来通知共享数据的状态信息的机制。由于涉及共享数据,因此条件变量是结合互斥量来使用的。

创建和摧毁条件变量

POSIX用pthread_cond_t类型的变量来表示条件变量。程序必须在使用pthread_cond_t变量之前对其进行初始化。对那些静态分配的、使用默认属性的pthread_cond_t变量来说,可以直接将PTHREAD_COND_INITIALIZER赋给变量就可以完成初始化。对那些动态分配的或不使用默认属性的变量来说,就要调用pthread_cond_init函数来执行初始化。该函数的形式为:

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 

参数attr是一个条件变量属性对象,如果将NULL传递给attr,则初始化一个具有默认属性的条件变量,否则,就要用与线程属性对象类似的方式,先创建一个条件变量属性对象,再设置它。如果成功,pthread_cond_init返回0。

函数pthread_cond_destroy销毁一个条件变量。该函数的形式为:

int pthread_cond_destroy(pthread_cond_t *cond); 

如果成功,pthread_cond_destroy就返回0。如果不成功,它就返回一个非零的错误码。

等待和通知条件变量

条件变量是与断言或条件测试一同调用的,条件变量这个名称就是从这个事实中引申出来的。通常,线程会对一个断言进行测试,如果测试失败,就调用pthread_cond_wait。函数pthread_cond_timedwait可以用来等待一段有限的时间。这两个函数的第一个参数cond是一个指向条件变量的指针,第二个参数mutex是一个指向互斥量的指针。线程在调用等待条件变量的函数之前,应该拥有这个互斥量。当线程被放置在条件变量的等待队列中时,等待操作会释放这个互斥量。pthread_cond_timedwait函数的第三个参数是一个指向返回时间的指针,如果条件变量信号没有在此之前出现的话,线程就在这个时间返回。注意,这个值表示的是绝对时间,而不是时间间隔。这两个函数的形式为:

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); 
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 

如果成功,这两个函数返回0。如果不成功,它们返回一个非零的错误码。如果abstime指定的时间已经到期了,pthread_cond_timedwait就返回ETIMEDOUT。

当另一个线程修改了可能会使断言成真的变量时,它应该唤醒一个或多个在等待断言成真的线程。pthread_cond_signal函数只唤醒一个阻塞在cond指向的条件变量上的线程。pthread_cond_broadcast函数解除所有阻塞在cond指向的条件变量上的线程。这两个函数的形式为:

int pthread_cond_broadcast(pthread_cond_t *cond); 
int pthread_cond_signal(pthread_cond_t *cond); 

如果成功,这两个函数返回0,如果不成功,它们返回一个非零的错误码。等待条件变量的线程在被唤醒后会等待对应的互斥量,在它获得了互斥量之后,它才会被复原成就绪状态。

条件变量应用实例

#include <stdio.h> 
#include <pthread.h> 
 	
int i = 0; 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER; 
void *threadfunc(void *pvoid) 
{
	while(1) 
	{
		pthread_mutex_lock(&mutex); 
		if(i < 200)
		{	
			i++;
			pthread_cond_signal(&condvar);
			pthread_mutex_unlock(&mutex);
		}
		else
		{
			pthread_mutex_unlock(&mutex);
			break;
		}
	}
}
int main()
{
	pthread_t tid;
	pthread_create(&tid,NULL,&threadfunc,NULL);
	pthread_nutex_lock(&mutex);
	while(i<100)
	{
		pthread_cond_wait(&condvar,&mutex);
	}
	printf("i = %d\n",i);
	pthread_nutex_unlock(&mutex);
	pthread_join(tid,NULL);
	return 0;
}

这是一个简单的条件变量应用程序。主线程监视全局变量i的值,如果i小于100,则等待。子线程递增i的值,直到i等于200。主线程直到i 大于或等于100之后,才能继续执行。运行该程序会发现,程序的输出结果可能是“i = 100”,也可能是“i = xxx”,其中xxx是一个100到200之间的数。这是因为子线程每次唤醒条件变量并释放互斥量之后,将与主线程一同竞争互斥量。当i大于100时,如果主线程获得互斥量,就会显示i的值。也就是说,等待条件变量的线程在被唤醒时,并不自动获得互斥量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值