【8】互斥量

①为什么需要互斥量
    1.共享变量(shared variable): 多个线程共享的变量
    2.多个线程访问共享变量所带来的问题
        线程A    线程B    global_cnt
        L                    
        U         L            5
        S         S            6
                   U            6
        当A将寄存器的值+1=6后还未写入全局变量时,线程B读全局变量5到寄存器;这项两个线程操作完后
          全局变量的值为6,而分别执行了两次加操作
            ·Load :将共享变量 global_cnt 从内存加载进寄存器,简称 L 。
            ·Update :更新寄存器里面的 global_cnt 值,执行加 1 操作,简称 U 。
            ·Store :将新的值,从寄存器写回到共享变量 global_cnt 的内存地址,简称为 S 。    
    3.所以要求
        临界区内,在同一时间只允许一个线程执行
        
②互斥量的接口
    [1]互斥量初始化
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
        int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);        
            1.如果互斥量是动态分配的,或者需要设定互斥量的属性,那么使用宏初始化的方法就不适用了,
            2.调用pthread_mutex_init()之后,互斥量处于没有加锁的状态。
        
    [2]互斥量的销毁
        · 使用 PTHREAD_MUTEX_INITIALIZER 初始化的互斥量无须销毁。
        · 不要销毁一个已加锁的互斥量,或者是真正配合条件变量使用的互斥量。
        · 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。    
        int pthread_mutex_destroy(pthread_mutex_t *mutex);    
    [3]互斥量的加锁和解锁(对于一把锁: 加锁的线程必定是解锁的线程)
        int pthread_mutex_lock(pthread_mutex_t *mutex);
            (1)调用 pthread_lock()的时候,可能会发生如下事件
                · 互斥量处于未锁定的状态,该函数会将互斥量锁定,同时返回成功。
                · 发起函数调用时,其他线程已锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,
                    那么 pthread_lock ()调用会陷入阻塞,等待互斥量解锁。
            
            (2)等待的过程中,如果互斥量持有线程解锁互斥量,可能会发生如下事件:
                · 函数调用线程是唯一等待者,获得互斥量,成功返回。
                · 函数调用线程不是唯一等待者,但成功获得互斥量,返回。
                · 函数调用线程不是唯一等待者,没能获得互斥量,继续阻塞,等待下一轮。
            
            (3) 如果在调用 pthread_lock ()线程时,之前已经调用过 pthread_lock ()且已经持有了互斥量,则根据互斥锁的类型,
                存在以下三种可能:
                ·PTHREAD_MUTEX_NORMAL :这是默认类型的互斥锁,这种情况下会发生死锁,调用线程永久阻塞,线程组的其他线程也无法申请到该互斥量。
                ·PTHREAD_MUTEX_ERRORCHECK :第二次调用 pthread_mutex_lock 函数时返回 EDEADLK 。
                ·PTHREAD_MUTEX_RECURSIVE :这种类型的互斥锁内部维护有引用计数,允许锁的持有者再次调用加锁操作。            
        int pthread_mutex_trylock(pthread_mutex_t *mutex);
          如果互斥量已然被锁定,那么当即返回 EBUSY 错误,而不像pthread_mutex_lock ()接口一样陷入阻塞。
        int pthread_mutex_timedlock(pthread_mutex_t?*restrict mutex, const struct timespec *restrict abs_timeout);
          如果申请互斥量的时候,互斥量已被锁定,那么等待;如果到了 abs_timeout 指定的时间,仍然没有申请到互斥量,那么返回ETIMEOUT 错误        
        int pthread_mutex_unlock(pthread_mutex_t *mutex);
        
③临界区的大小
    ·临界区的范围不能太小,如果太小,可能起不到保护的目的。
    ·临界区也不能太大,临界区的代码不能并发,如果临界区太大,就无法充分利用多处理器发挥多线程的优势。
    ·不要将不相干的(特别是可能陷入阻塞的)代码放入临界区内执行。    

④互斥量的性能
    如果临界区非常小,线程之间对临界区的竞争并不激烈,只会偶尔发生,这种情况下,忙 - 等待的策略要优于互斥量的 “ 让出 CPU ,陷入阻塞,等待唤醒 ” 的策略。
    采用忙 - 等待策略的锁为自旋锁。
    
⑤互斥锁的公平性

    1.公平: 如果A在B之前调用 lock()方法,那么A应该先于B获得锁,进入临界区。
    2.互斥锁不是一把公平的锁,并没有做到先来先服务。
    
⑥互斥锁的类型
    (1)类型
    ·PTHREAD_MUTEX_TIMED_NP
    ·PTHREAD_MUTEX_RECURSIVE
    ·PTHREAD_MUTEX_ERRORCHECK
    ·PTHREAD_MUTEX_ADAPTIVE_NP
    
    (2)查询和设置互斥锁的类型
        int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
        int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);

⑦死锁和活锁

    (1)死锁
        线程1已经成功拿到了互斥量1,正在申请互斥量2,而同时在另一个CPU上,线程2已经拿到了互
        斥量2,正在申请互斥量1。彼此占有对方正在申请的互斥量,结局就是谁也没办法拿到想要的互斥
        量,于是死锁就发生了。线程1和线程2都会陷入阻塞。
        
    (2)活锁
        1.trylock 不行就回退的思想有可能会引发活锁( live lock )。
            线程 1 首先申请锁 mutex_a 后,之后尝试申请 mutex_b ,失败以后,释放 mutex_a
            进入下一轮循环,同时线程 2 会因为尝试申请 mutex_a 失败,而释放 mutex_b ,
        2.如果两个线程恰好一直保持这种节奏,就可能在很长的时间内两者都一次次地擦肩而过。
            线程1        线程2
            有锁a        有锁b
            获锁b        获锁a
            trylock        trylock        // 两个线程都没有获取到对应的锁
            释放锁a        释放锁b        // 等待下一轮循环

⑧未使用互斥量,访问共享资源的结果

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int g_j = 100;
void *start_routime1(void *arg)
{	
	while(1) {
		printf("%d\n", ++g_j); 
		sleep(1);
	}
		
	pthread_exit(NULL);
}
void *start_routime2(void *arg)
{	
	while(1) {
		printf("%d\n", --g_j); 
		sleep(1);
	}
	pthread_exit(NULL);
}
int main(int argc, char **argv)
{
	pthread_t thid1, thid2;

	
	if(pthread_create(&thid1, NULL, start_routime1, NULL))
		perror("pthread_create err");
	if(pthread_create(&thid2, NULL, start_routime2, NULL))
		perror("pthread_create err");
	pthread_join(thid1, NULL);	// 线程未退出陷入阻塞
	pthread_join(thid2, NULL);

	return 0;
}

/* 执行结果: 两个线程同时在操作共享资源, 导致后面的结果不可预测
	101
	100
	101
	99	 // 线程1在执行+1的过程中,线程2执行了2次-1操作
	102
	101
	100
	101
*/

⑨使用互斥量,访问共享资源的结果

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

/* 使用锁1和锁2效果相同 */
/* 初始化一个互斥量 */
// 使用宏初始化的互斥量无需销毁, 使用pthread_mutex_init()创建的互斥量需要手动销毁
//static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;	// 使用默认属性,互斥量处于解锁状态

static pthread_mutex_t mutex2;


int g_j = 100;
void *start_routime1(void *arg)
{	
	while(1) {
		pthread_mutex_lock(&mutex2);
		printf("%d\n", g_j++); 
		pthread_mutex_unlock(&mutex2);
		sleep(1);
	}
		
	pthread_exit(NULL);
}
void *start_routime2(void *arg)
{	
	pthread_mutexattr_t attr;
	int type = -1;
	int type2 = -1;
	
	// 查询互斥量的类型: 线程锁的默认类型为PTHREAD_MUTEX_NORMAL, 基本上都用这个锁
	pthread_mutexattr_gettype(&attr, &type);
	printf("type = %d\n", type);	//PTHREAD_MUTEX_NORMAL = 0

	// 设置线程锁的类型: 为PTHREAD_MUTEX_ERRORCHECK_NP, 只是为了演示设置方法, 一般用默认的锁
	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
	pthread_mutexattr_gettype(&attr, &type2);
	printf("type2 = %d\n", type2);	//PTHREAD_MUTEX_ERRORCHECK_NP = 2

	while(1) {
		pthread_mutex_lock(&mutex2);
		printf("%d\n", g_j--); 
		pthread_mutex_unlock(&mutex2);
		sleep(1);
	}
	pthread_exit(NULL);
}
int main(int argc, char **argv)
{
	pthread_t thid1, thid2;
	
	pthread_mutex_init(&mutex2, NULL); //使用默认属性,互斥量处于解锁状态
	
	if(pthread_create(&thid1, NULL, start_routime1, NULL))
		perror("pthread_create err");
	if(pthread_create(&thid2, NULL, start_routime2, NULL))
		perror("pthread_create err");
	pthread_join(thid1, NULL);	// 线程未退出陷入阻塞
	pthread_join(thid2, NULL);
      pthread_mutex_destroy(&mutex2);
	return 0;
}

/* 运行结果 : 	// 一次计算只会+1或者-1, 保证了同一时间只有一个线程在操作共享资源
book@gui_hua_shu:$ ./a.out
	101
    type = 0
    type2 = 2
	100
	99
	100
	99
	100
	99
	100
	101
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值