线程同步的方法1——互斥锁、信号量

目录

一、引入

二、利用多线程同步解决线程并发

三、线程同步的概念

四、互斥锁

4.1互斥锁接口

4.2全局变量++正确性问题(引例)

4.3 互斥锁例2(共享资源(打印机)使用问题)

五、信号量

5.1 信号量接口

5.2 全局变量++正确性问题

5.3 信号量例2


一、引入

创建5个线程,修改和读取全局变量,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>


int wg=0;
void* fun(void* arg)
{
	for(int i=0;i<1000;i++)
	{
		wg++;
		printf("wg=%d\n",wg);
	}
}

int main()
{
	pthread_t id[5];
	for(int i=0;i<5;i++)
	{
		pthread_create(&id[i],NULL,fun,NULL);
	}
	for(int i=0;i<5;i++)
	{
		pthread_join(id[i],NULL);
	}
	exit(0);
}

运行结果: 

正确结果应该是5000,程序运行结果错误。

原因:因为wg++不是一个原子操作;

原子操作的概念:一个或多个指令的序列,对外是不可分的;即没有其他进程可以看到其中间状态或者中断此操作;

而wg++不是一个原子操作;
这种情况也不是每一次都发生,也不是一定发生了多少次,这种情况就是最可怕的,给人的感觉是时对时错;

注意,只有一个处理器的时候这种情况出现的概率是非常小的,同一时刻只有一个线程在运行,不容易出现两个线程同时去获取i的值的情况,但是也会发生; 

二、利用多线程同步解决线程并发

多线程并发就有可能出现问题,比如两个线程都去在链表中插入,比如都在做尾插,都在找尾巴,那么多线程就会出现问题。

三、线程同步的概念


一个进程中的所有线程共享同一个地址空间和诸如打开的文件之类的其他资源.一个线程对资源的任何修改都会影响同一个进程中其他线程的环境,因此,需要同步各种线程的活动,以便它们互不干涉且不破坏数据结构.例如,如果两个线程都试图同时往一个双向链表中增加一个元素,则可能会丢失一个元素或者破坏链表结构.

同步就是让所有线程按照一定的规则执行,使得其正确性和效率都有迹可循,线程同步的手段就是对线程之间的穿插进行控制.

线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行。 

线程同步的方法:

  • 互斥锁
  • 信号量
  • 条件变量、
  • 读写锁

四、互斥锁

4.1互斥锁接口

int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);//attr:锁的属性,不需要传空即可

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread mutex_t *mutex);

int pthread_mutex_destroy(pthread mutex_t *mutex);

注意,互斥锁mutex都需要传地址,因为要改变它;

上述函数成功返回0,失败返回错误码。

pthread_mutex_destroy函数用于销毁互斥锁,以释放其占用的内核资源,销毁一个已经加锁的互斥锁将导致不可预期的后果。

4.2全局变量++正确性问题(引例)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>


int wg=0;

pthread_mutex_t mutex;
void* fun(void* arg)
{
	for(int i=0;i<1000;i++)
	{
		pthread_mutex_lock(&mutex);
		wg++;
		pthread_mutex_unlock(&mutex);
		printf("wg=%d\n",wg);
	}
}

int main()
{
	pthread_t id[5];
	pthread_mutex_init(&mutex,NULL);
	for(int i=0;i<5;i++)
	{
		pthread_create(&id[i],NULL,fun,NULL);
	}
	for(int i=0;i<5;i++)
	{
		pthread_join(id[i],NULL);
	}
	pthread_mutex_destroy(&mutex);
	exit(0);
}

4.3 互斥锁例2(共享资源(打印机)使用问题)

主线程和函数线程模拟访问打印机,主线程输出第一个字符'A'表示开始使用打印机,输出第二个字符'A'表示结束使用,函数线程操作与主线程相同。

(由于打印机同一时刻只能被一个线程使用,所以输出结果不应该出现ABAB交替出现)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t mutex;
void* thread_fun(void* arg)
{
	for(int i=0;i<5;i++)
	{
		pthread_mutex_lock(&mutex);
		write(1,"B",1);
		int n=rand()%3;	
		sleep(n);
		write(1,"B",1);
		pthread_mutex_unlock(&mutex);

		 n=rand()%3;
		sleep(n);
	}
	pthread_exit(NULL);
}

int main()
{
	pthread_t id;
	pthread_mutex_init(&mutex,NULL);
	pthread_create(&id,NULL,thread_fun,NULL);
	for(int i=0;i<5;i++)
	{
		pthread_mutex_lock(&mutex);
		write(1,"A",1);
		int n=rand()%3;
		sleep(n);
		write(1,"A",1);
		pthread_mutex_unlock(&mutex);

		n=rand()%3;
		sleep(n);
	}
	pthread_mutex_destroy(&mutex);
	pthread_join(id,NULL);
	exit(0);
}

五、信号量

5.1 信号量接口

sem_t 全局定义一个sem_t类型的信号量。

注意,必须要加头文件:#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);//信号量的初始化

作用:在sem指向的地址初始化未命名的信号量。

value参数指定信号量的初始值(第三个参数);

pshared:设置信号量是否在进程间共享,Linux不支持,一般给0;(非0为共享) 

int sem wait(sem t*sem)://P操作,wait表示等待,相当于是等待获取资源,那么就是P操作

int sem_post(sem_t *sem);//V操作

int sem_destroy(sem_t *sem);//销毁信号量

上述函数成功返回0,失败返回错误码。 

5.2 全局变量++正确性问题

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
int wg=0;
sem_t sem;
void* fun(void* arg)
{
	for(int i=0;i<1000;i++)
	{
		sem_wait(&sem);
		wg++;
		sem_post(&sem);
		printf("wg=%d\n",wg);
	}
}

int main()
{
	pthread_t id[5];
	sem_init(&sem,0,1);
	for(int i=0;i<5;i++)
	{
		pthread_create(&id[i],NULL,fun,NULL);
	}
	for(int i=0;i<5;i++)
	{
		pthread_join(id[i],NULL);
	}
	sem_destroy(&sem);
	exit(0);
}

5.3 信号量例2

主线程获取用户输入,函数线程将用户输入的数据存储到文件中。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <fcntl.h>
sem_t sem1;
sem_t sem2;
char buff[128]={0};

void* PthreadFun(void* arg)
{
	int fd=open("a.txt",O_RDWR|O_CREAT,0664);
	assert(fd!=-1);

	while(1)
	{
		sem_wait(&sem2);
		if(strncmp(buff,"end",3)==0)
		{
			break;
		}
		write(fd,buff,strlen(buff));
		memset(buff,0,128);
		sem_post(&sem1);
	}
	sem_destroy(&sem1);
	sem_destroy(&sem2);
}

int main()
{
	sem_init(&sem1,0,1);
	sem_init(&sem2,0,0);

	pthread_t id;
	int res=pthread_create(&id,NULL,PthreadFun,NULL);
	assert(res==0);

	while(1)
	{
		sem_wait(&sem1);
		printf("please input data:");
		fflush(stdout);

		fgets(buff,128,stdin);
		buff[strlen(buff)-1]=0;
		sem_post(&sem2);
		if(strncmp(buff,"end",3)==0)
		{
			break;
		}
	}
	pthread_exit(NULL);
}

运行结果: 

5.4 信号量例3

循环打印ABC

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

sem_t sem1;
sem_t sem2;
sem_t sem3;
void* funa(void* arg)
{
	for(int i=0;i<5;i++)
	{
		sem_wait(&sem1);
		printf("A");
		fflush(stdout);
		sem_post(&sem2);
		sleep(1);
	}
}

void* funb(void* arg)
{
	for(int i=0;i<5;i++)
	{
		sem_wait(&sem2);
		printf("B");
		fflush(stdout);
		sem_post(&sem3);
		sleep(1);
	}
}

void* func(void* arg)
{
	for(int i=0;i<5;i++)
	{
		sem_wait(&sem3);
		printf("C");
		fflush(stdout);
		sem_post(&sem1);
		sleep(1);
	}
}

int main()
{
	pthread_t id[3];
	sem_init(&sem1,0,1);
	sem_init(&sem2,0,0);
	sem_init(&sem3,0,0);
	pthread_create(&id[0],NULL,funa,NULL);
	pthread_create(&id[1],NULL,funb,NULL);
	pthread_create(&id[2],NULL,func,NULL);

	for(int i=0;i<3;i++)
	{
		pthread_join(id[i],NULL);
	}
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	sem_destroy(&sem3);
	exit(0);
}

线程同步其它方法见此博客:线程同步的方法2——条件变量、读写锁 

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值