线程同步互斥和锁(十三)

一 线程通信

  由于线程是共享内存的,故线程之间的通信其实也就是资源共享,不像进程之间的通信。其实更多的是线程之间的同步与互斥。
  1、使用全局变量:主要由于多个线程可能更改全局变量,因此全局变量最好声明为violate。
  2、使用消息实现通信。
  3、使用事件CEvent类实现线程间通信。

二 线程同步互斥

  互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
  同步:主要是流程上的概念,是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

互斥锁,自旋锁,读写锁各自的应用场景是什么?底层是怎么实现多线程间的互斥的?

三 互斥锁(mutex)

  互斥锁(Mutual exclusion,Mutex)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区域(critical section)达成。临界区域指的是一块对公共资源进行访问的代码,并非一种机制或是算法。一个程序、进程、线程可以拥有多个临界区域,但是并不一定会应用互斥锁。
  互斥锁主要用于实现内核中的互斥访问功能。对它的访问必须遵循一些规则:同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行解锁。互斥锁不能进行递归锁定或解锁。
  Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁:
  一般条件变量需要结合互斥量一起使用:
  1、初始化锁:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  其中参数 mutexattr 用于指定锁的属性(见下),如果为NULL则使用缺省属性。
  互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前有四个值可供选择:
  (1)PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
  (2)PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
  (3)PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
  (4)PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
  2、阻塞加锁:

int pthread_mutex_lock(pthread_mutex *mutex);

  3、非阻塞加锁:

int pthread_mutex_trylock( pthread_mutex_t *mutex);

  该函数语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
  4、解锁(要求锁是lock状态,并且由加锁线程解锁):

int pthread_mutex_unlock(pthread_mutex *mutex);

返回值:成功返回0,失败返回错误号。
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
  5、销毁锁(此时锁必需unlock状态,否则返回EBUSY):

int pthread_mutex_destroy(pthread_mutex *mutex);
#include <stdio.h>
#include <pthread.h>

void *ticketsell1(void *);
void *ticketsell2(void *);
int tickets = 20;
pthread_mutex_t mutex;

int main()
{
	pthread_t id1,id2;
	pthread_mutex_init(&mutex, NULL);//
	int error;

	error = pthread_create(&id1, NULL, ticketsell1, NULL);
	if(error != 0)
	{
		printf("pthread is not created!\n");
		return -1;
	}

	error = pthread_create(&id2, NULL, ticketsell2, NULL);
	if(error != 0)
	{
		printf("pthread is not created!\n");
		return -1;
	}

	pthread_join(id1,NULL);
	pthread_join(id2,NULL);
	
	return 0;
}

void *ticketsell1(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);//给互斥量上锁
		if(tickets > 0)
		{
			usleep(1000);
			printf("ticketse1 sells ticket:%d\n",tickets--);
			pthread_mutex_unlock(&mutex);//给互斥量解锁
			
		}
		else
		{
			pthread_mutex_unlock(&mutex);//给互斥量解锁
			break;
		}
		pthread_yield();//线程调度函数,使每个线程都有执行机会
	}
	return (void *)0;
}

void *ticketsell2(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);//给互斥量上锁
		if(tickets > 0)
		{
			usleep(1000);
			printf("ticketse2 sells ticket:%d\n",tickets--);
			pthread_mutex_unlock(&mutex);//给互斥量解锁
		}
		else
		{
			pthread_mutex_unlock(&mutex);//给互斥量解锁
			break;
		}
		pthread_yield();//线程调度函数,是两个线程都有执行机会
	}

	return (void *)0;
}

  线程同步,严格来说,是程序通过专用的机制来保证多个并行执行的线程在同一时刻不会执行指定的程序段。
  代码的线程安全,指的是多线程以安全执行的方式操作共享数据结构。或者简单的说,就是程序在多线程环境下运行而不会引发数据错误。有多种策略实现线程安全的数据结构。
  线程同步是现实线程安全的一种手段。
  条件变量:与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
  条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。

四 条件变量

  条件变量是利用线程间共享全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。
  1、初始化条件变量
  int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
  尽管POSIX标准中为条件变量定义了属性,但在Linux中没有实现,因此cond_attr值通常为NULL,且被忽略。
  2、有两个等待函数
  (1)无条件等待
  int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  (2)计时等待
  int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex *mutex, const timespec *abstime);
  如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。

无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求(用 pthread_cond_wait() 或 pthread_cond_timedwait() 请求)竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

  3、激发条件
  (1)激活一个等待该条件的线程(存在多个等待线程时按入队顺序激活其中一个)
  int pthread_cond_signal(pthread_cond_t *cond);
  (2)激活所有等待线程
  int pthread_cond_broadcast(pthread_cond_t *cond);
  4、销毁条件变量
  int pthread_cond_destroy(pthread_cond_t *cond);
  只有在没有线程在该条件变量上等待的时候才能销毁这个条件变量,否则返回EBUSY。
  说明
  1、pthread_cond_wait自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发(变量为ture)。在调用pthread_cond_wait之前,应用程序必须加锁互斥量。pthread_cond_wait函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)。
  2、互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争——个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用pthread_cond_wait函数(block)之间被发出,从而造成无限制的等待)。
  3、条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用pthread_cond_signalpthread_cond_boardcast函数,可能导致调用线程死锁。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
 
void *ticketsell1(void *);
void *ticketsell2(void *);
int tickets = 20;
pthread_mutex_t mutex;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;//静态初始化条件变量;
		
int main()
{
	pthread_t id1,id2;
	pthread_mutex_init(&mutex, NULL);
	int error;
 
	error = pthread_create(&id1, NULL, ticketsell1, NULL);
	if(error != 0)
	{
		printf("pthread is not created!\n");
		return -1;
	}
 
	error = pthread_create(&id2, NULL, ticketsell2, NULL);
	if(error != 0)
	{
		printf("pthread is not created!\n");
		return -1;
	}
 
	pthread_join(id1, NULL);
	pthread_join(id2, NULL);
	
	return 0;
}
 
void *ticketsell1(void *arg)
{
	pthread_mutex_lock(&mutex);
	while(tickets > 0)
	{
		if(tickets%2 == 1)
		{
			usleep(1000);
			printf("ticketse1 sells ticket:%d\n", tickets--);
			pthread_cond_signal(&qready);//条件改变,发送信号,通知ticketse2
		}
		else
		{
			pthread_cond_wait(&qready, &mutex);//解开Mutex,并等待qready改变
		}
		pthread_mutex_unlock(&mutex);//给互斥量解锁
	}
	return (void *)0;
}
 
void *ticketsell2(void *arg)
{
	pthread_mutex_lock(&mutex);
	while(tickets > 0)
	{
		if(tickets%2 == 0)
		{
			usleep(1000);
			printf("ticketse2 sells ticket:%d\n", tickets--);
			pthread_cond_signal(&qready);//条件改变,发送信号,通知ticketse1
		}
		else
		{
			pthread_cond_wait(&qready, &mutex);//解开mutex, 并等待qready改变
		}
		pthread_mutex_unlock(&mutex);//给互斥量解锁
	}
 
	return (void *)0;
}

  条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件变量发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
  函数pthread_cond_wait使线程阻塞在一个条件变量上,而函数pthread_cond_signal是用来释放被阻塞在条件变量上的一个线程。但是要注意的是,条件变量只是起到阻塞和唤醒线程的作用,具体的判断条件还需用户给出,我这里给出的是tickets是否是偶数这个条件。

五 信号量(semaphore)

  如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。
  线程使用的基本信号量函数有四个:
  #include <semaphore.h>
  1、初始化信号量
  int sem_init (sem_t *sem , int pshared, unsigned int value);
  参数:
  sem - 指定要初始化的信号量;
  pshared - 信号量 sem 的共享选项,linux只支持0,表示它是当前进程的局部信号量;
  value - 信号量 sem 的初始值。
  2、信号量值加1
  给参数sem指定的信号量值加1。
  int sem_post(sem_t *sem);
  3、信号量值减1
  给参数sem指定的信号量值减1。
  int sem_wait(sem_t *sem);
  如果sem所指的信号量的数值为0,函数将会等待直到有其它线程使它不再是0为止。
  4、销毁信号量
  销毁指定的信号量。
  int sem_destroy(sem_t *sem);

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

void *ticketsell1(void *);
void *ticketsell2(void *);
int tickets = 20;
sem_t mutex, full;

int main()
{
	int ret = sem_init(&mutex, 0 ,1);//初始化mutex信号量为1
 	ret += sem_init(&full, 0 ,0);//初始化full信号量为0
 	if(ret != 0)
 	{
  		printf("sem_init fails!\n");
 	}
 	error = pthread_create(&id1, NULL, ticketsell1, NULL);
	 if(error != 0)
	 {
	 	printf("pthread is not created!\n");
	 	return -1;
	 }
	 error = pthread_create(&id2, NULL, ticketsell2, NULL);
	 if(error != 0)
	 {
		 printf("pthread is not created!\n");
		 return -1;
	 }
	thread_join(id1,NULL);
 	pthread_join(id2,NULL);
 	return 0;
}

void *ticketsell1(void *arg)
{
	while(1)
	{
		sem_wait(&mutex);//mutex信号量进行P操作
		if(tickets > 0)
		{
			usleep(1000);
			printf("ticketse1 sells ticket:%d\n",tickets--);
			sem_post(&full);//full信号量进行V操作
		}
		else
		{
			sem_post(&full);//full信号量进行V操作
			break;
		}
	}
	return (void *)0;
}
void *ticketsell2(void *arg)
{
	while(1)
	{
		sem_wait(&full);//full信号量进行P操作
		if(tickets > 0)
		{
			usleep(1000);
			printf("ticketse2 sells ticket:%d\n",tickets--);
			sem_post(&mutex);//mutex信号量进行V操作
		}
		else
		{
			sem_post(&mutex);//mutex信号量进行V操作
			break;
		}
	}
	return (void *)0;
}

  上面的sem_init函数用来初始化两个信号量的初始化值,这里一个设为1,一个设为0,sem_wait类似于P操作,让信号量减1,如果小于结果小于0,线程阻塞,否则线程继续执行,sem_post类似于V操作,提升信号量的值,加1,通过这两个信号量之间的互相“救对方”,就可以实现这两个线程的同步执行。
  信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

六 读写锁

  说明:

  1、如果一个线程用读锁锁定了临界区,那么其他线程也可以用读锁来进入临界区,这样就可以多个线程并行操作。但这个时候,如果再进行写锁加锁就会发生阻塞,写锁请求阻塞后,后面如果继续有读锁来请求,这些后来的读锁都会被阻塞!这样避免了读锁长期占用资源,防止写锁饥饿!
  2、如果一个线程用写锁锁住了临界区,那么其他线程不管是读锁还是写锁都会发生阻塞!

  1、初始化:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

  2、读写加锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);

  3、销毁锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

/* 初始化读写锁 */
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
/* 全局资源 */
int global_num = 10;

void err_exit(const char *err_msg)
{
 printf("error:%s\n", err_msg);
 exit(1);
}

/* 读锁线程函数 */
void *thread_read_lock(void *arg)
{
 char *pthr_name = (char *)arg;

 while (1)
 {
   /* 读加锁 */
     pthread_rwlock_rdlock(&rwlock);

     printf("线程%s进入临界区,global_num = %d\n", pthr_name, global_num);
     sleep(1);
     printf("线程%s离开临界区...\n", pthr_name);

     /* 读解锁 */
     pthread_rwlock_unlock(&rwlock);

     sleep(1);
 }

 return NULL;
}

/* 写锁线程函数 */
void *thread_write_lock(void *arg)
{
 char *pthr_name = (char *)arg;

 while (1)
 {
     /* 写加锁 */
     pthread_rwlock_wrlock(&rwlock);

     /* 写操作 */
     global_num++;
     printf("线程%s进入临界区,global_num = %d\n", pthr_name, global_num);
     sleep(1);
     printf("线程%s离开临界区...\n", pthr_name);

     /* 写解锁 */
     pthread_rwlock_unlock(&rwlock);

     sleep(2);
 }

 return NULL;
}

int main(void)
{
 pthread_t tid_read_1, tid_read_2, tid_write_1, tid_write_2;

 /* 创建4个线程,2个读,2个写 */
 if (pthread_create(&tid_read_1, NULL, thread_read_lock, "read_1") != 0)
     err_exit("create tid_read_1");

 if (pthread_create(&tid_read_2, NULL, thread_read_lock, "read_2") != 0)
     err_exit("create tid_read_2");

 if (pthread_create(&tid_write_1, NULL, thread_write_lock, "write_1") != 0)
     err_exit("create tid_write_1");

 if (pthread_create(&tid_write_2, NULL, thread_write_lock, "write_2") != 0)
     err_exit("create tid_write_2");

 /* 随便等待一个线程,防止main结束 */
 if (pthread_join(tid_read_1, NULL) != 0)
     err_exit("pthread_join()");

 return 0;
}

  读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
  这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
  读写锁适合于对数据结构的读次数比写次数多得多的情况。因为,读模式锁定时可以共享,以写模式锁住时意味着独占,所以读写锁又叫共享-独占锁。
  初始化和销毁:

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

  成功则返回0,出错则返回错误编号。
  同互斥量以上,在释放读写锁占用的内存之前,需要先通过pthread_rwlock_destroy对读写锁进行清理工作,释放由init分配的资源。
  读和写:

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

  成功则返回0,出错则返回错误编号。
  这3个函数分别实现获取读锁,获取写锁和释放锁的操作。获取锁的两个函数是阻塞操作,同样,非阻塞的函数为:

#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

成功则返回0,出错则返回错误编号。
非阻塞的获取锁操作,如果可以获取则返回0,否则返回错误的EBUSY

七 读写信号量(rw_semaphore)

  读写信号量对访问者进行了细分,或者为读者,或者为写者,读者在保持读写信号量期间只能对该读写信号量保护的共享资源进行读访问,如果一个任务除了需要读,可能还需要写,那么它必须被归类为写者,它在对共享资源访问之前必须先获得写者身份,写者在发现自己不需要写访问的情况下可以降级为读者。读写信号量同时拥有的读者数不受限制,也就说可以有任意多个读者同时拥有一个读写信号量。如果一个读写信号量当前没有被写者拥有并且也没有写者等待读者释放信号量,那么任何读者都可以成功获得该读写信号量;否则,读者必须被挂起直到写者释放该信号量。如果一个读写信号量当前没有被读者或写者拥有并且也没有写者等待该信号量,那么一个写者可以成功获得该读写信号量,否则写者将被挂起,直到没有任何访问者。因此,写者是排他性的,独占性的。

八 自旋锁(Spanlock)

  自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
  信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用(_trylock的变种能够在中断上下文使用),而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共巷资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。
  自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。
  跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。
  无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。
  自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。
  对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
  在单处理机环境中可以使用特定的原子级汇编指令swaptest_and_set实现进程互斥,(swap指令:交换两个内存单元的内容;test_and_set指令取出内存某一单元(位)的值,然后再给该单元(位)赋一个新值,可能引发的问题:
  由于test_and_set指令对内存的两次操作都需要经过总线,在执行test_and_set指令之前锁住总线,在执行test_and_set指令后开放总线,即可保证test_and_set指令执行的原子性。

do{
b=1;
while(b){
lock(bus);
b = test_and_set(&lock);
unlock(bus);
}
临界区
lock = 0;
其余部分
}while(1)

过多占用cpu资源:如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了。因此,一般自旋锁实现会有一个参数限定最多持续尝试次数。超出后,自旋锁放弃当前time slice。等下一次机会。

九 参考文章

linux c线程间同步(通信)的几种方法–互斥锁,条件变量,信号量,读写锁:https://blog.csdn.net/vertor11/article/details/55657619
Linux中的各种锁:https://blog.csdn.net/shenwansangz/article/details/50450769
自旋锁、阻塞锁、可重入锁、悲观锁、乐观锁、读写锁、偏向所、轻量级锁、重量级锁、锁膨胀、对象锁和类锁:https://blog.csdn.net/a314773862/article/details/54095819
深入理解各种锁:https://www.jianshu.com/p/5725db8f07dc

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值