系统编程六:线程同步互斥+有名信号量+无名信号+互斥锁

问题引入:

例子:两条线程同时访问某一片内存空间导致数据践踏。

#include<stdio.h>
#include <pthread.h>

int g_val = 0;

//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine1(void *arg)
{
	g_val = 100;
	sleep(1);//延时1秒,此时线程2就执行,g_val = 200;
	
	printf("routine1 100  g_val:%d\n",g_val);
}

//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine2(void *arg)
{

	sleep(1);//延时1秒,让线程1先开始执行,此时 g_val = 100;
	g_val = 200;

	printf("routine2 200  g_val:%d\n",g_val);
}

int main()
{
	// 创建一个新的线程1
	pthread_t thread1;	
	pthread_create(&thread1,NULL,routine1,NULL);					  
	// 创建一个新的线程2
	pthread_t thread2;	
	pthread_create(&thread2,NULL,routine2,NULL);	
	

	//接合子线程 --阻塞等待子线程退出 回收资源
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);

	return 0;
}

结果:

如何去避免 不同线程之间 访问同一个资源 导致 的数据践踏问题呢??

也就是说,线程1在访问这个资源的时候,别的线程不能访问,要等线程1访问结束之后,别的线程才能访问。 -----使用线程同步互斥机制。

一、线程同步互斥

1、什么是同步互斥呢? 为什么要处理同步互斥?

        同步互斥 就是使得线程处理任务时有先后顺序,为了防止线程资源被抢占的问题。

2、处理同步互斥方式有哪些?

信号量  ------》进程
有名信号量 ---》进程
无名信号量 ---》线程
互斥锁  ------》线程
读写锁 -------》线程 

二、同步互斥方式之一-----有名信号量

1、什么是有名信号量

        有名信号量 跟 信号量非常相似,信号量的值只能是 0/1,但是有名信号量的值可以是 0~正无穷。
信号量 使用了 空间 + 数据 ,有名信号量 只是使用了数据来处理。

2、有名信号量的函数接口

1)创建 并且打开一个有名信号量 ?? --sem_open

NAME
       sem_open - initialize and open a named semaphore
            //初始化 和 打开 一个有名信号量
SYNOPSIS
    #include <fcntl.h>           /* For O_* constants */
    #include <sys/stat.h>        /* For mode constants */
    #include <semaphore.h>

    sem_t *sem_open(const char *name, int oflag);
    sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);

参数:
        name:有名信号量的名字 ,要求必须以 "/"开头,比如 "/sem_test" ,存在于 /dev/shm
        oflag:
                O_CREAT --->不存在就创建 
                O_EXCL ----》存在了就报错
        mode:八进制权限 0777  0666
        value:    有名信号量的初始值

注意: 如果有 oflag中有 O_CREAT选项,则参数 mode 和 value 必须要填写。
如果有名信号量存在了,但是你又写了O_CREAT,那么你后面填写的mode和value就不会起作用。

返回值: 
        成功返回  有名信号量的地址 
        失败  SEM_FAILED
        
2)有名信号量的P操作   ---》sem_wait

p操作: 资源数 -1 操作 

    #include <semaphore.h>
    int sem_wait(sem_t *sem);

参数: 
        sem:有名信号量的地址 
        
返回值: 
        成功返回 0 
        失败返回 -1
如果 当前的信号量的值 是 2 ,那么     sem_wait 就会马上返回,这个值就变成了 1
如果 当前的信号量的值 是 1 ,那么     sem_wait 就会马上返回,这个值就变成了 0
如果 当前的信号量的值 是 0 ,那么     sem_wait 就会阻塞等待,一直阻塞到有名信号量的值 >0的时候

3)有名信号量的V操作   ---》sem_post        

V操作:资源数 +1 操作  ---》一定是可以+1的,不会阻塞
    #include <semaphore.h>
    int sem_post(sem_t *sem);

参数: 
        sem:有名信号量的地址 
        
返回值: 
        成功返回 0 
        失败返回 -1    


4)关闭有名信号量。---》sem_close

    #include <semaphore.h>

    int sem_close(sem_t *sem);

参数: 
        sem:有名信号量的地址 
        
返回值: 
        成功返回 0 
        失败返回 -1    
        
5)删除有名信号量 ---》sem_unlink

NAME
       sem_unlink - remove a named semaphore
        //删除有名信号量
SYNOPSIS
       #include <semaphore.h>
       int sem_unlink(const char *name);

参数: 
        name:有名信号量的名字  sem_test

返回值: 
        成功返回 0 
        失败返回 -1

3、练习

将之前 的 信号量 + 共享内存两个进程的通信 -----》变成 有名信号量 +  共享内存

07使用共享内存+信号量实现通信1.c    ----》使用共享内存+有名信号量实现通信1.c
08使用共享内存+信号量实现通信2.c    ----》使用共享内存+有名信号量实现通信2.c 

//02使用共享内存+有名信号量实现通信1.c
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h> 
	
#define SEM_NAME	"/sem_test" 

int main()
{
	//1)先获取 key值。
	key_t key = ftok(".",10);
	
	//2)根据key值 获取 共享内存的ID号,如果说key值对应的共享内存不存在,则创建
	int shmid = shmget(key,1024,IPC_CREAT|0666);
	//3)根据ID号将共享内存 映射 到 本进程虚拟内存空间的某个区域 
	char*shm_p = shmat(shmid, NULL, 0);
	
	//使用有名信号量 协调 两个进程之间通信
	//1)创建 并且打开一个有名信号量 
	sem_t *sem = sem_open(SEM_NAME, O_CREAT,0777, 0);
	//int count = 0;
	
	while(1)
	{	
		scanf("%s",shm_p); 

		//把车开进去,有车了  数据+1  V操作 --》sem_post
		sem_post(sem);
					   
		//退出的时候  byebye
		if(strncmp(shm_p,"byebye",6) == 0)
		{	
			sleep(1);	
			break;
		}
		//printf("count:%d\n",count++);
	}
	
	//最后不用的时候,解除映射 
	shmdt(shm_p);
	//5)当没有进程再需要使用这一块共享内存时,删除释放它 
	shmctl(shmid,IPC_RMID, NULL);
	
	//4)关闭有名信号量。---》sem_close
	sem_close(sem);
	//5)删除有名信号量 ---》sem_unlink
	sem_unlink(SEM_NAME);
	
	return 0;
}
//03使用共享内存+有名信号量实现通信2.c
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
	#include <fcntl.h>           /* For O_* constants */
	#include <sys/stat.h> 
	
#define SEM_NAME	"/sem_test"	
	
int main()
{
	//1)先获取 key值。
	key_t key = ftok(".",10);
	
	//2)根据key值 获取 共享内存的ID号,如果说key值对应的共享内存不存在,则创建
	int shmid = shmget(key,1024,IPC_CREAT|0666);
	//3)根据ID号将共享内存 映射 到 本进程虚拟内存空间的某个区域 
	char*shm_p = shmat(shmid, NULL, 0);
	
	//使用有名信号量 协调 两个进程之间通信
	//1)创建 并且打开一个有名信号量 
	sem_t *sem = sem_open(SEM_NAME, O_CREAT,0777, 0);
	
	
	while(1)
	{
		//把车提出来, 数据 -1   P操作
		//如果没车,也就是数据为0 ,不能进行P操作 此时该函数就会阻塞
		sem_wait(sem);
		
		printf("%s\n",shm_p);
		//退出的时候  byebye
		if(strncmp(shm_p,"byebye",6) == 0)
			break;
	}
	
	//最后不用的时候,解除映射 
	shmdt(shm_p);

	return 0;
}

三、无名信号量 ----线程同步互斥

        一般作用于 线程之间的互斥,由于是无名信号量,所以说是没有名字的,不能使用 sem_open 打开。

1、无名信号量的函数接口

1) 定义一个无名信号量 (数据类型 sem_t)
      sem_t sem; ---无名信号量  --》变量 

2) 初始化无名信号量 ---》       man  3 sem_init 
      
NAME
       sem_init - initialize an unnamed semaphore
                
SYNOPSIS
       #include <semaphore.h>

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

参数: 
        sem --》无名信号量的变量的地址
        pshared---》
                    0 --》作用于 线程之间  ---只考虑这种情况 
                    非0 --》作用于 进程之间 
        value --》无名信号量 初始化的值            

返回值: 
        成功返回 0 
        失败返回 -1    

3) 无名信号量的P操作   ---》sem_wait

p操作: 资源数 -1 操作 

    #include <semaphore.h>
    int sem_wait(sem_t *sem);

参数: 
        sem:无名信号量的变量的地址 
        
返回值: 
        成功返回 0 
        失败返回 -1
如果 当前的信号量的值 是 2 ,那么     sem_wait 就会马上返回,这个值就变成了 1
如果 当前的信号量的值 是 1 ,那么     sem_wait 就会马上返回,这个值就变成了 0
如果 当前的信号量的值 是 0 ,那么     sem_wait 就会阻塞等待,一直阻塞到有名信号量的值 >0的时候

4)无名信号量的V操作   ---》sem_post        

V操作:资源数 +1 操作  ---》一定是可以+1的,不会阻塞
    #include <semaphore.h>
    int sem_post(sem_t *sem);

参数: 
        sem:无名信号量的变量的地址 
        
返回值: 
        成功返回 0 
        失败返回 -1    

5)销毁 无名信号量 ----》man  3 sem_destroy 

       sem_destroy - destroy an unnamed semaphore

SYNOPSIS
       #include <semaphore.h>
       int sem_destroy(sem_t *sem);

参数: 
        sem:无名信号量的变量的地址 
        
返回值: 
        成功返回 0 
        失败返回 -1

2、练习

        把  两个线程 访问 同一个资源 发生 数据 践踏的问题  使用 无名信号量 进行 解决。
        也就是说,  线程 1 在 使用 g_val变量的时候,也就是访问该变量所在的内存空间的时候 ,别的线程 都不可以访问 。

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


int g_val = 0;
//1) 定义一个无名信号量 (数据类型 sem_t)
sem_t sem; 
	
//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine1(void *arg)
{
	//无名信号量P操作 资源数 -1
	//如果当前资源数为 0 此时不能减1,该函数会阻塞等待资源数
	sem_wait(&sem);
	
	g_val = 100;
	sleep(1);//延时1秒,此时线程2就执行,g_val = 200;
	
	printf("routine1 100  g_val:%d\n",g_val);
	
	//无名信号量 V操作  资源数+1
	sem_post(&sem);
}

//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine2(void *arg)
{
	//无名信号量P操作 资源数 -1
	//如果当前资源数为 0 此时不能减1,该函数会阻塞等待资源数
	sem_wait(&sem);	
	sleep(1);//延时1秒,让线程1先开始执行,此时 g_val = 100;
	g_val = 200;

	printf("routine2 200  g_val:%d\n",g_val);
	
	//无名信号量 V操作  资源数+1
	sem_post(&sem);
}

int main()
{
	//2) 初始化无名信号量
	sem_init(&sem, 0,1);
	  
	// 创建一个新的线程1
	pthread_t thread1;	
	pthread_create(&thread1,NULL,routine1,NULL);					  
	// 创建一个新的线程2
	pthread_t thread2;	
	pthread_create(&thread2,NULL,routine2,NULL);	
	

	//接合子线程 --阻塞等待子线程退出 回收资源
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);

	//销毁 无名信号量
	sem_destroy(&sem);
	
	return 0;
}

四、线程互斥方式之一----互斥锁

1、什么是互斥锁

互斥锁 是专门 用于处理线程互斥的一种方式,它有两种状态: 上锁状态/解锁状态。

特点:如果互斥锁处于上锁状态,那么再上锁就会造成阻塞,直到这把锁解开之后,才能上锁。
解锁状态依然可以解锁,不会阻塞。

2、关于线程互斥锁的函数接口

1)定义互斥锁变量。 -----》数据类型  pthread_mutex_t 
    pthread_mutex_t mutex;

2)初始化 互斥锁  ----pthread_mutex_init

NAME
      initialize a mutex

SYNOPSIS
       #include <pthread.h>

       int pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t * attr);
       pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化

参数: 
        mutex:互斥锁变量的地址 
        attr: 互斥锁的属性, 默认属性  为NULL
        
3)上锁 ----》pthread_mutex_lock

    #include <pthread.h>
    int pthread_mutex_lock(pthread_mutex_t *mutex);

参数: 
        mutex:互斥锁变量的地址 

返回值: 
        成功返回 0 
        失败返回 错误码 

4)解锁 
    int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数: 
        mutex:互斥锁变量的地址 

返回值: 
        成功返回 0 
        失败返回 错误码 

5)销毁互斥锁  ---》pthread_mutex_destroy 
    
    #include <pthread.h>

    int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数: 
        mutex:互斥锁变量的地址 

返回值: 
        成功返回 0 
        失败返回 错误码 

3、互斥锁的使用场景

当我们使用一些临界资源时,防止多个线程同时访问,我们可以这么做,在线程访问临界资源前,让当前这个线程先上锁,
然后再访问资源,访问完之后就解锁,让别的线程去上锁。

4、练习

将 04线程使用无名信号量实现同步互斥.c ----->改成 05线程使用互斥锁实现同步互斥.c 

//05线程使用互斥锁实现同步互斥.c
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>


int g_val = 0;
//1)定义互斥锁变量。 -----》数据类型  pthread_mutex_t 
pthread_mutex_t mutex;
	
//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine1(void *arg)
{
	pthread_mutex_lock(&mutex);//上锁
	
	g_val = 100;
	sleep(1);//延时1秒,此时线程2就执行,g_val = 200;
	
	printf("routine1 100  g_val:%d\n",g_val);
	
	pthread_mutex_unlock(&mutex);//解锁
}

//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine2(void *arg)
{

	pthread_mutex_lock(&mutex);//上锁

	sleep(1);//延时1秒,让线程1先开始执行,此时 g_val = 100;
	g_val = 200;

	printf("routine2 200  g_val:%d\n",g_val);
	
	pthread_mutex_unlock(&mutex);//解锁
}

int main()
{
	//2)初始化 互斥锁
	 pthread_mutex_init(&mutex,NULL);
	  
	// 创建一个新的线程1
	pthread_t thread1;	
	pthread_create(&thread1,NULL,routine1,NULL);					  
	// 创建一个新的线程2
	pthread_t thread2;	
	pthread_create(&thread2,NULL,routine2,NULL);	
	

	//接合子线程 --阻塞等待子线程退出 回收资源
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);

	
	//5)销毁互斥锁
	pthread_mutex_destroy(&mutex);
	return 0;
}

//06线程使用互斥锁实现同步互斥.c
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>


int g_val = 0;
//1)定义互斥锁变量。 -----》数据类型  pthread_mutex_t 
pthread_mutex_t mutex;
	
//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine1(void *arg)
{
	pthread_mutex_lock(&mutex);//上锁
	g_val = 100;
	int i;
	//如果当前线程1 已经 上锁,那么在线程1 跑的 这 20S之内 ,主线程 发送了 取消请求给它 ,那么这个时候线程1取消了 
	//记得,在子线程退出之前(响应 取消请求 之前,先把锁解开)
	for(i=0; i<20; i++)
	{
		sleep(1);		
		printf("%d  routine1 100  g_val:%d\n",i,g_val);		
	}

	pthread_mutex_unlock(&mutex);//解锁
}

//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine2(void *arg)
{
	sleep(1);//强行延时1S,让上面的子线程1 先跑 
	pthread_mutex_lock(&mutex);//上锁
	g_val = 200;
	int i;
	
	for(i=0; i<20; i++)
	{
		sleep(1);		
		printf("routine2 200  g_val:%d\n",g_val);		
	}

	pthread_mutex_unlock(&mutex);//解锁
}

//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine3(void *arg)
{
	//这里我是没有上锁的 
	int i;
	
	for(i=0; i<5; i++)
	{
		sleep(1);		
		printf("routine3\n");		
	}
}

int main()
{
	//2)初始化 互斥锁
	 pthread_mutex_init(&mutex,NULL);
	  
	// 创建一个新的线程1
	pthread_t thread1;	
	pthread_create(&thread1,NULL,routine1,NULL);					  
	// 创建一个新的线程2
	pthread_t thread2;	
	pthread_create(&thread2,NULL,routine2,NULL);	
	// 创建一个新的线程3
	//pthread_t thread3;	
	//pthread_create(&thread3,NULL,routine3,NULL);	

	sleep(5);
	//发送取消请求给 子线程1 
	pthread_cancel(thread1);
	
	//接合子线程 --阻塞等待子线程退出 回收资源
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);
	//pthread_join(thread3,NULL);
	
	//5)销毁互斥锁
	pthread_mutex_destroy(&mutex);
	return 0;
}

五、线程的取消处理 例程 函数 

#include <pthread.h>

       void pthread_cleanup_push(void(*routine)(void *), void *arg);
函数作用:压栈的取消处理例程 ,也就是说,在线程 收到 取消请求的时候 ,让 线程先执行 取消例程 函数  routine
    
参数:    
        routine :线程的取消例程 函数 ,线程收到取消请求之后,会执行这个函数再 退出(也就是再响应 取消请求 )    
        arg: 给例程函数传递的参数 
        
        
       void pthread_cleanup_pop(int execute);

函数作用:弹栈的取消处理例程 ,也就是说,如果线程 没有收到 取消请求 ,那么也会根据 参数 execute 决定 是否 执行  取消例程 函数

参数: 
        execute---》 0 --》表示 该线程正常退出的时候, 不执行 例程函数  
                     非0---》表示 该线程正常退出的时候, 也执行 例程函数  

注意:上面两个函数是成对出现 ,配套使用

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


int g_val = 0;
//1)定义互斥锁变量。 -----》数据类型  pthread_mutex_t 
pthread_mutex_t mutex;
	
//线程的取消处理例程
//线程收到取消 请求的时候 ,先执行这个函数 ,再退出
void exitRoutine(void *arg)
{
	printf("exitRoutine\n");
	//解锁 
	pthread_mutex_unlock(&mutex);//解锁
}	
	
//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine1(void *arg)
{
	//先压栈   压栈的取消处理例程 
	pthread_cleanup_push(exitRoutine,NULL);
	
	pthread_mutex_lock(&mutex);//上锁
	g_val = 100;
	int i;
	//如果当前线程1 已经 上锁,那么在线程1 跑的 这 20S之内 ,主线程 发送了 取消请求给它 ,那么这个时候线程1取消了 
	//记得,在子线程退出之前(响应 取消请求 之前,先把锁解开)
	for(i=0; i<20; i++)
	{
		sleep(1);		
		printf("%d  routine1 100  g_val:%d\n",i,g_val);		
	}

	pthread_mutex_unlock(&mutex);//解锁
	//弹栈
	pthread_cleanup_pop(0);
}

//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine2(void *arg)
{
	sleep(1);//强行延时1S,让上面的子线程1 先跑 
	//先压栈   压栈的取消处理例程 
	pthread_cleanup_push(exitRoutine,NULL);
	pthread_mutex_lock(&mutex);//上锁
	g_val = 200;
	int i;
	
	for(i=0; i<20; i++)
	{
		sleep(1);		
		printf("routine2 200  g_val:%d\n",g_val);		
	}

	pthread_mutex_unlock(&mutex);//解锁
	//弹栈
	pthread_cleanup_pop(0);
}

//线程的例程函数,也就是创建线程之后,去执行这个函数
void* routine3(void *arg)
{
	//这里我是没有上锁的 
	int i;
	
	for(i=0; i<5; i++)
	{
		sleep(1);		
		printf("routine3\n");		
	}
}

int main()
{
	//2)初始化 互斥锁
	 pthread_mutex_init(&mutex,NULL);
	  
	// 创建一个新的线程1
	pthread_t thread1;	
	pthread_create(&thread1,NULL,routine1,NULL);					  
	// 创建一个新的线程2
	pthread_t thread2;	
	pthread_create(&thread2,NULL,routine2,NULL);	
	// 创建一个新的线程3
	//pthread_t thread3;	
	//pthread_create(&thread3,NULL,routine3,NULL);	

	sleep(5);
	//发送取消请求给 子线程1 
	pthread_cancel(thread1);
	
	//接合子线程 --阻塞等待子线程退出 回收资源
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);
	//pthread_join(thread3,NULL);
	
	//5)销毁互斥锁
	pthread_mutex_destroy(&mutex);
	return 0;
}

六、线程 与 进程 的 区别

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值