Linux —— 线程互斥

Linux知识总结目录索引

文章目录

  • 一、互斥锁
    • 1. 操作步骤
      • (1)创建锁
      • (2)初始化锁
        • ①设置线程的属性
      • (3)上锁 && 解锁
      • (4)销毁互斥锁
    • 2. 死锁
      • (1)死锁的两种情况:
      • (2)避免的死锁的原则
      • (3)临界区代码原则
    • 3. 互斥锁和信号量的区别
    • 4. 线程安全和可重入
  • 二、自旋锁
    • 1. 操作步骤
    • 2. 自旋锁和互斥锁的区别
  • 三、读写锁
    • 1. 读写锁的规则
    • 2. 操作步骤
    • 3. 代码实现

一、互斥锁

为啥要有互斥?

  1. 多个进程/线程执行的先后顺序不确定,何时切出CPU也不确定。
  2. 多个进程/线程访问变量的动作往往不是原子的。

1. 操作步骤

(1)创建锁

// 创建互斥锁mutex
pthread_mutex_t mutex;

(2)初始化锁

  在Linux下, 线程的互斥量数据类型是pthread_mutex_t 在使用前, 要对它进行初始化:

初始化的两种方法:(推荐使用第二种)
1.静态分配

pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER;

2.动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  1. mutex: 要初始化的互斥量(restrict的作用是告诉调用者,不要改变指针的指向)

  2. attr:锁的属性,一般写NULL

restrict的作用:只用于修饰函数参数里的指针,这个指针会频繁使用,所以把这个地址放到寄存器里,用着好找。

①设置线程的属性

int pthread_attr_init(pthread_attr_t *attr);//初始化线程属性
int pthread_attr_destroy(pthread_attr_t *attr);//销毁线程属性

Thread attributes(线程属性):
    线程的分离属性:      Detach state=PTHREAD_CREATE_DETACHED
    线程的竞争范围:      Scope = PTHREAD_SCOPE_SYSTEM
    是否继承调度策略:    Inherit scheduler = PTHREAD_EXPLICIT_SCHED
    调度策略:           Scheduling policy = SCHED_OTHER
    调度优先级:         Scheduling priority = 0
    线程栈之间的保留区域: Guard size = 4096 bytes
    自己指定栈地址:       Stack address = 0x40197000
    栈大小:              Stack size = 0x3000000 bytes

//设置线程的分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

//detachstate:有以下两种选择
PTHREAD_CREATE_DETACHED:设置成分离态
PTHREAD_CREATE_JOINABLE:设置成可结合态

//获取线程的分离属性
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
//int *detachstate:输出型参数,将分离属性存放在该变量里


(3)上锁 && 解锁

  对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后,要对互斥量进行解锁。

  具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。

锁粒度(尽量小):临界区操作个数,执行时间的长短

int pthread_mutex_lock(pthread_mutex_t *mutex);
//加锁:如果是1,置0,返回
//     如果是0,阻塞

int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞加锁

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

返回值: 成功则返回0, 出错则返回错误编号.


(4)销毁互斥锁

对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.

注意:

  1. 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁
  2. 不要销毁一个已经加锁的互斥量
  3. 已销毁的互斥量要确保后面不会有线程尝试加锁
pthread_mutex_destroy(&lock);                      //销毁

返回值: 成功则返回0, 出错则返回错误编号.

说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解。


2. 死锁

(1)死锁的两种情况:

  1. 情况1:
      如果两个线程先后调用两次lock,第二次调用lock时,由于锁已被占用,该线程会挂起等待别的线程释放锁,然后锁正是被自己占用着的,该线程又被挂起不能释放锁,因此就永远处于挂起等待状态了,这就叫死锁。

  2. 情况2:
      有线程A、B。A获得锁1,B获得锁2,此时A调用lock企图获得锁2,结果是需要挂起等待B释放锁2,而此时B也调用了lock企图获得锁1,结果是B挂起等待A释放锁1,于是乎A、B永远处于挂起状态。

(2)避免的死锁的原则

  死锁主要发生在有多个依赖锁存在时,会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生.如何避免死锁是使用互斥量应该格外注意的东西。
总体来讲, 有几个不成文的基本原则:

  1. 对共享资源操作前一定要获得锁。

  2. 完成操作以后一定要释放锁。

  3. 尽量短时间地占用锁。

  4. 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。

  5. 线程错误返回时应该释放它所获得的锁。

  6. 写程序是尽量避免同时获得多个锁,如果一定要这么做,则遵循一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见是按mutex变量的地址顺序)获得锁,则不会出现死锁。

  mutex互斥信号量锁住的不是一个变量,而是阻塞住一段程序。如果对一个mutex变量testlock,执行了第一次pthread_mutex_lock(testlock)之后,在unlock之前的这段时间内,如果有其他线程也执行到了pthread_mutex_lock,这个线程就会阻塞住,直到之前的线程unlock之后才能执行,由此,实现同步,也就达到保护临界区资源的目的。

  为了实现互斥操作,大多数体系结构提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据交换,由于只有一条指令,保证了原子性。即使是多处理器平台,访问内存的总线周期也有先后,一个处理器的交换指令执行时另一个处理器的交换指令只能等待总线周期。

(3)临界区代码原则

  1. 短——临界区代码简洁明了;
  2. 平——临界区代码逻辑清晰,没有复杂的函数调用尤其是尽量不要申请其他互斥资源;
  3. 快:临界区代码执行速度快。

3. 互斥锁和信号量的区别

  1. 互斥量用于线程的互斥,信号线用于线程的同步。
    这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

  1. 互斥量值只能为0/1,信号量值可以为非负整数
    也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

  2. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。


4. 线程安全和可重入

可重入函数:在多个执行流中被同时调用不会存在问题。
线程安全函数:在多线程中被同时调用不会存在问题。
可重入函数一般情况下都是线程安全的
线程安全函数不一定是可重入函数


二、自旋锁

1. 操作步骤

//1. 定义自旋锁
pthread_spinlock_t spin;

//2. 初始化自旋锁
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

//3. 上锁
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);

//4. 解锁
 int pthread_spin_unlock(pthread_spinlock_t *lock);

//5. 销毁锁
int pthread_spin_destroy(pthread_spinlock_t *lock);

2. 自旋锁和互斥锁的区别

  互斥锁是当阻塞在pthread_mutex_lock时,放弃CPU,好让别人使用CPU。自旋锁阻塞在pthread_spin_lock时,不会释放CPU,不断向cup询问可以用了不。


三、读写锁

1. 读写锁的规则

  1. 读读共享
  2. 读写排他
  3. 写写排他
  4. 写优先级高
    Linux —— 线程互斥_第1张图片

2. 操作步骤

// 1. 定义锁 
pthread_rwlock_t lock;

// 2. 初始化
pthread_rwlock_init(&lock, NULL);

// 3. 读锁 
pthread_rwlock_rdlock(&lock);
pthread_rwlock_wrlock(&lock);

// 4. 解锁 
pthread_rwlock_unlock(&lock);

// 5. 销毁锁
pthread_rwlock_destroy(&lock);

3. 代码实现

#include 
#include 
#include 
#include 

int g_count;
pthread_rwlock_t rw;

void* route_read(void* arg)
{
	int id = *(int*)arg;
	free(arg);
	while(1)
	{
		pthread_rwlock_rdlock(&rw);
		printf("%d read_pthread : %d\n", id, g_count);
		pthread_rwlock_unlock(&rw);
		usleep(100000);
	}
}

void* route_write(void* arg)
{
	int id = *(int*)arg;
	free(arg);
	while(1)
	{
		pthread_rwlock_wrlock(&rw);
		printf("%d write_pthread : %d\n", id, ++g_count);
		pthread_rwlock_unlock(&rw);
		usleep(100000);
	}
}

int main()
{
	pthread_t w1, r1, r2, r3;
	pthread_rwlock_init(&rw, NULL);

	int* p = (int*)malloc(sizeof(int));
	*p = 1;
	pthread_create(&w1, NULL, route_write, p);

	int* p1 = (int*)malloc(sizeof(int));
	*p1 = 1;
	pthread_create(&r1, NULL, route_read, p1);

	int* p2 = (int*)malloc(sizeof(int));
	*p2 = 2;
	pthread_create(&r2, NULL, route_read, p2);


	int* p3 = (int*)malloc(sizeof(int));
	*p3 = 3;
	pthread_create(&r3, NULL, route_read, p3);

	pthread_join(w1, NULL);
	pthread_join(r1, NULL);
	pthread_join(r2, NULL);
	pthread_join(r3, NULL);

	pthread_rwlock_destroy(&rw);
}

你可能感兴趣的:(Linux知识总结,线程互斥,互斥锁,自旋锁,读写锁)