线程互斥量、线程信号量 编程案例

本文介绍了线程同步的概念,强调了在多线程环境中对公共资源访问的同步控制。通过实例展示了线程互斥量和线程信号量的使用,解释了如何利用它们解决线程安全问题,防止数据错乱。文中详细阐述了互斥量的加锁和解锁操作,以及信号量的wait和post函数,确保了线程对共享数据的正确访问。

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

目录 

一:线程同步

二:线程互斥量 & 线程信号量


一:线程同步

两个(或多个)线程同时执行时,经常需要访问到【公共资源】或【代码的关键部分】,这时就涉及到了线程的同步问题(CPU随机轮片,线程争抢资源)

我们可以通过下面两种方法来更好地控制线程的执行情况和更好地访问代码的关键部分(在访问代码的关键部分执行的时候,其他线程不能去争抢资源)

线程信号量

线程互斥量

二:线程互斥量 & 线程信号量

因为多个线程同时执行 而且还访问同样的数据 此时就产生了线程同步 线程同步归根结底就是数据的不安全 这个问题就必须要去解决

线程执行随机 执行顺序随机 执行时间随机 【CPU随机轮片】

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string.h>
using namespace std;

typedef struct student
{
	char stuname[20];
	char sexy[6];
}STU;

STU stu = { 0 };

void* pthread_function1(void* pv)
{
	int num = 0;
	while (1)
	{
		if (num % 2 == 0)
		{
			strcpy(stu.stuname, "陈茹涵");
			strcpy(stu.sexy, "男生");
		}
		else
		{
			strcpy(stu.stuname, "林夕梦");
			strcpy(stu.sexy, "女生");
		}
		num++;
		//sleep(1);//这里只是两个线程 如果很多线程 并且 不知道线程执行时间 这就完全不可行
	}
}

void* pthread_function2(void* pv)
{
	while (1)
	{
		cout  << "stu.stuname = "<<stu.stuname << "stu.sexy = " << stu.sexy << endl;
		//sleep(1);//这里只是两个线程 如果很多线程 并且 不知道线程执行时间 这就完全不可行
	}
}

int main()
{
	pthread_t threadid1 = 0;
	pthread_t threadid2 = 0;

	pthread_create(&threadid1, NULL, pthread_function1, NULL);
	pthread_create(&threadid1, NULL, pthread_function2, NULL);

	while (1)
	{

	}

	return 0;
}

出现问题如:姓名和性别不对应 

原来正确的对应关系是     陈茹涵 男生    林夕梦 女生

可是打印出来的会出现有 陈茹涵  女生    林夕梦 男生 的错误情境

原因就是:线程执行随机 执行顺序随机 执行时间随机(CPU随机轮片,线程争抢资源)

线程互斥量

用互斥量进行同步

使用互斥量是实现多线程程序中的同步访问的另一种手段

程序员给某个对象加上一把“锁”,每次只允许一个线程去访问它

如果想对代码关键部分的访问进行控制,你必须在进入这段代码之前锁定一把互斥量,在完成操作后再打开它

互斥量函数组

互斥量对象用pthread_mutex_t表示,相关函数都在<pthread.h>头文件里面声明

线程互斥量解决线程同步问题 

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string.h>
using namespace std;

typedef struct student
{
	char stuname[20];
	char sexy[6];
}STU;

STU stu = { 0 };

//互斥量
pthread_mutex_t mutex;

void* pthread_function1(void* pv)
{
	int num = 0;
	while (1)
	{
		if (num % 2 == 0)
		{
			pthread_mutex_lock(&mutex);//加锁 阻塞式函数 只有非加锁状态才可以进行加锁
			strcpy(stu.stuname, "陈茹涵");
			strcpy(stu.sexy, "男生");
			pthread_mutex_unlock(&mutex);//解锁
		}
		else
		{
			pthread_mutex_lock(&mutex);
			strcpy(stu.stuname, "林夕梦");
			strcpy(stu.sexy, "女生");
			pthread_mutex_unlock(&mutex);
		}
		num++;
		//sleep(1);
	}
}

void* pthread_function2(void* pv)
{
	while (1)
	{
		pthread_mutex_lock(&mutex);
		cout  << "stu.stuname = "<<stu.stuname << "stu.sexy = " << stu.sexy << endl;
		pthread_mutex_unlock(&mutex);
		//sleep(1);
	}
}

int main()
{
	pthread_t threadid1 = 0;
	pthread_t threadid2 = 0;

	//互斥量初始化
	pthread_mutex_init(&mutex,NULL);

	pthread_create(&threadid1, NULL, pthread_function1, NULL);
	pthread_create(&threadid1, NULL, pthread_function2, NULL);

	while (1)
	{

	}
    //如果是服务器放服务器停止代码 如果是客户端放程序结束的时候
    //pthread_mutex_destroy(&mutex);

	return 0;
}

通过互斥量 解决 线程同步问题 

结果可以看出,姓名和性别的对应关系都是正确的

小结

1.锁加在哪里:线程哪些代码在"触碰"共享数据的代码,加锁在操作共享数据之前,解锁在操作共享数据之后

2.哪些线程需要加锁:所有对共享数据的操作的线程都要加锁

错误情况

1.所有线程都必须加锁,只要有一个线程没有加锁,其他都作废

2.避免死锁(互斥量保证有且只有一个) 

线程信号量

用信号量进行同步

“信号量”的作用就像是代码周围的门卫

二进制信号量:一种最简单的一种信号量,只有“0”,“1”两种取值

计数信号量:有更大的取值范围,一般用于希望有限个线程去执行一段给定的代码

头文件 <semaphore.h>

信号量函数的名字都以“sem_”打头

信号量对象用sem_t表示

创建一个信号量

作用:对给定的信号量对象进行初始化

函数原型

int sem_init(sem_t *sem,int pshared,unsigned value);

函数参数

sem: 要进行初始化的信号量对象

pshared:控制着信号量的类型,如果值为0,表示它是当前进程的局部信号量;否则,其他进程就能够共享这个信号量

value:赋给信号量对象的一个整数类型的初始值

调用成功时 返回 0;

sem_post函数-解锁

作用:给信号量的值加上一个“1”

函数原型

int sem_post(sem_t *sem);

函数参数

sem: 初始化的信号量对象的指针作为参数,用来改变该对象的值

调用成功时 返回 0;

是一个“原子操作”-即同时对同一个信号量做加“1”操作的两个线程是不会冲突的。信号量的值永远会正确地加上一个“2”,因为有两个线程试图改变它

sem_wait函数-加锁

作用:从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法

函数原型 

int sem_wait(sem_t *sem);

函数参数

sem: 初始化的信号量对象的指针作为参数,用来改变该对象的值

调用成功时 返回 0;

也是一个“原子操作”; 

sem_destroy函数 清理

作用:用完信号量后,对该信号量进行清理 

函数原型

int sem_destroy(sem_t *sem);

函数参数 

sem: 初始化的信号量对象的指针作为参数,用来改变该对象的值

调用成功时 返回 0;

归还自己占有的一切资源,在清理信号量的时候如果还有线程在等待它,用户就会收到一个错误

线程信号量 解决 线程同步问题 

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string.h>
#include<semaphore.h>
using namespace std;

typedef struct student
{
	char stuname[20];
	char sexy[6];
}STU;

STU stu = { 0 };

//线程信号量
sem_t sem;


void* pthread_function1(void* pv)
{
	int num = 0;
	while (1)
	{
		if (num % 2 == 0)
		{
			sem_wait(&sem);//加锁
			strcpy(stu.stuname, "陈茹涵");
			strcpy(stu.sexy, "男生");
			sem_post(&sem);//解锁
		}
		else
		{
			sem_wait(&sem);
			strcpy(stu.stuname, "林夕梦");
			strcpy(stu.sexy, "女生");
			sem_post(&sem);
		}
		num++;
		//sleep(1);
	}
}

void* pthread_function2(void* pv)
{
	while (1)
	{
		sem_wait(&sem);
		cout  << "stu.stuname = "<<stu.stuname << "stu.sexy = " << stu.sexy << endl;
		sem_post(&sem);
		//sleep(1);
	}
}

int main()
{
	pthread_t threadid1 = 0;
	pthread_t threadid2 = 0;

	//线程信号量初始化
	sem_init(&sem, 0, 1);

	pthread_create(&threadid1, NULL, pthread_function1, NULL);
	pthread_create(&threadid1, NULL, pthread_function2, NULL);

	while (1)
	{

	}

    //如果是服务器放服务器停止代码 如果是客户端放程序结束的时候
    //sem_destroy(&mutex);

	return 0;
}

测试:

姓名和性别的对应关系都是正确的,不会出现有线程争抢资源,数据错乱的问题 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chenruhan_QAQ_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值