<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>路上 &#187; 读写锁</title>
	<atom:link href="http://www.speedvi.net/tag/%e8%af%bb%e5%86%99%e9%94%81/feed" rel="self" type="application/rss+xml" />
	<link>http://www.speedvi.net</link>
	<description>为者常成 行者常至</description>
	<lastBuildDate>Sat, 12 Jun 2010 06:30:36 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>用于线程同步的读写锁（Readers/writer locks）</title>
		<link>http://www.speedvi.net/2010/02/21/207.html</link>
		<comments>http://www.speedvi.net/2010/02/21/207.html#comments</comments>
		<pubDate>Sun, 21 Feb 2010 08:21:44 +0000</pubDate>
		<dc:creator>行者</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[同步]]></category>
		<category><![CDATA[系统内核]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[读写锁]]></category>

		<guid isPermaLink="false">http://www.speedvi.net/2010/02/21/207.html</guid>
		<description><![CDATA[　　读写锁(readers/writer lock)的具体含义是文如其名：对一个资源有多个读者而无写入者，或者是只有一个写入者而没有其他的写入者或读出者。 　　这种情况是经常会用到的，这就需要有一种专用于这个目的的特殊的同步元素。 　　很多情况下，你需要有个数据结构在一堆线程之间共享。当然，你希望在一个时刻只能有一个线程可以对这个数据结构执行写入操作。如果同时有多个线程能对其写入，那么就有可能一个线程写入的数据会覆盖其他线程所写入的数据。为了防止这种情况发生，写线程可以用独占的方式获取“读写锁”，也就是说只有它才能够访问这个数据结构。不过需要注意这个访问的独占性严格受控于随意的方式。也就是说这是由系统设计者来处理的，是由系统设计者来确保所有访问那个数据区域的线程通过读写锁进行同步。 　　对于读操作则是相反地。由于读操作是非破坏性的，所以可有多个线程读取数据。这里很明确的一点就是当任何线程或任何多个线程在读取数据区域的时候不能够有线程对这个数据区域执行写操作。不然的话，当一个读线程在读取部分数据区域的时候被一个写线程抢占，之后再恢复运行，继续读取数据，读到的是更新的被更新后的数据，这样会产生混乱的，结果就是数据的不一致性。 　　下面看看使用读写锁用到的函数： 　　前两个函数是用来为读写锁初始化库的内部存储区域： int pthread_rwlock_init (pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr); int pthread_rwlock_destroy (pthread_rwlock_t *lock); 　　pthread_rwlock_init()函数使用了lock参数，比按照attr所设定的属性初始化这个参数。在我们的例子中，我们会使用NULL作为属性，这样就按照默认属性初始化。 　　当使用完读写锁之后，就需要使用pthread_rwlock_destory()函数对其销毁。你不能够使用一个未初始化或已被销毁的读写锁。 　　之后，我们需要获取合适类型的锁。如上所述，有两种基本类型的锁：读操作需要非独占的访问、写操作需要独占的访问。为了函数名易懂，函数名都是按照锁的类型进行命名的： int pthread_rwlock_rdlock (pthread_rwlock_t *lock); int pthread_rwlock_tryrdlock (pthread_rwlock_t *lock); int pthread_rwlock_wrlock (pthread_rwlock_t *lock); int pthread_rwlock_trywrlock (pthread_rwlock_t *lock); 　　这里一共有四个函数，而不是预想的两个。两个预想的函数是pthread_rwlock_rdlock()与pthread_rwlock_wrlock()，这两个函数分别用于读与写。这些是阻塞函数，当锁对于所选操作不可用的时候，线程就会阻塞。当锁在适当情况下可用，线程就是解除阻塞。由于线程通过该函数解除阻塞，就可以假设访问锁所保护的资源是安全的。 　　有些时候，线程可能不想被阻塞，而是查看一下它是否能够获得那个锁。这就是那个“尝试(try)”版本函数的由来。需要注意的是尝试版的函数在能够获取锁的时候会获取这个锁，而在不能获取的时候它们也不会阻塞线程，它只会返回一个错误标识。它们在能够获取锁的时候会获取锁的原因是简单的。假设有线程要获取这个锁来执行读取操作，不过不想在锁不可用的时候阻塞。这个线程就会调用pthread_rwlock_tryrdlock()函数，之后它被告知可以获得这个锁。如果这时pthread_rwlock_tryrdlock()函数不分配这个锁，就可能有坏事发生——另外一个能够抢占这个线程的线程执行了，并且这第二个线程可能会用不兼容的模式锁定了资源。由于第一个线程实际上没有获取这个锁，当其确实开始获取锁的时候，它可能就会使用pthread_rwlock_rdlock()函数，这样的话它就会被阻塞，因为该资源在这种模式下已经不可用了。所以，如果在可以获取锁的时候我们没有获取，即使使用了try版的函数，最终也有可能被阻塞。 　　最后，不论锁是被如何使用的，我们都要释放它： int pthread_rwlock_unlock (pthread_rwlock_t *lock); 　　一旦线程完成了对资源的操作之后，它必须通过调用这个函数释放锁。如果锁按照其他等待线程所需要的模式已经可用了，那么那个等待线程就会进入就绪状态。 　　需要注意我们不能够使用互斥体来完成这种类型的同步。互斥体是一个单线程的代理，这对于写操作是可以的，一旦涉及到读的情况就会完全失败，因为使用互斥体的话只能够允许一个线程执行读操作。而信号量也是不能使用的，因为使用它的话就不能分辨两者模式的访问了，信号量可以允许多个读线程的访问，不过当一个写线程试图获取它的时候，就和读线程获取它一样不能分辨出来，这样的话结果就是对一个资源可能会有多个读操作与一个或多个写操作同时存在，这是非常糟糕的！ Related posts:用于线程同步的Sleepon锁 　　在多线程程序中常遇到的另外一个情况就是让线程等待某件事的发生。这件事可以是任何事。它可以是设备上的数据就绪了，也可以是传送带到达了合适的位置或数据已经写入磁盘了，等等。另外还要讨论一下多个线程等待某个事件的情况。 　　为了实现这个功能，我们可以使用条件变量(condition variable)或是更简单的睡眠锁(sleepon lock)。 　　要使用睡眠锁，你需要执行几个操作。先看看要调用的函数，之后再看看你该如何使用这个锁： &#160; int [...]


Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/23/208.html' rel='bookmark' title='用于线程同步的Sleepon锁'>用于线程同步的Sleepon锁</a> <small>　　在多线程程序中常遇到的另外一个情况就是让线程等待某件事的发生。这件事可以是任何事。它可以是设备上的数据就绪了，也可以是传送带到达了合适的位置或数据已经写入磁盘了，等等。另外还要讨论一下多个线程等待某个事件的情况。 　　为了实现这个功能，我们可以使用条件变量(condition variable)或是更简单的睡眠锁(sleepon lock)。 　　要使用睡眠锁，你需要执行几个操作。先看看要调用的函数，之后再看看你该如何使用这个锁： &nbsp; int pthread_sleepon_lock (void); int...</small></li>
<li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='用于线程同步的条件变量(Condition Variables)'>用于线程同步的条件变量(Condition Variables)</a> <small>　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 &nbsp; /* * cp1.c */ #include...</small></li>
<li><a href='http://www.speedvi.net/2009/12/27/184.html' rel='bookmark' title='内核状态'>内核状态</a> <small>RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &nbsp; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。...</small></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>　　读写锁(readers/writer lock)的具体含义是文如其名：对一个资源有多个读者而无写入者，或者是只有一个写入者而没有其他的写入者或读出者。</p>
<p>　　这种情况是经常会用到的，这就需要有一种专用于这个目的的特殊的同步元素。</p>
<p>　　很多情况下，你需要有个数据结构在一堆线程之间共享。当然，你希望在一个时刻只能有一个线程可以对这个数据结构执行写入操作。如果同时有多个线程能对其写入，那么就有可能一个线程写入的数据会覆盖其他线程所写入的数据。为了防止这种情况发生，写线程可以用独占的方式获取“读写锁”，也就是说只有它才能够访问这个数据结构。不过需要注意这个访问的独占性严格受控于随意的方式。也就是说这是由系统设计者来处理的，是由系统设计者来确保所有访问那个数据区域的线程通过读写锁进行同步。</p>
<p><span id="more-207"></span>
<p>　　对于读操作则是相反地。由于读操作是非破坏性的，所以可有多个线程读取数据。这里很明确的一点就是当任何线程或任何多个线程在读取数据区域的时候不能够有线程对这个数据区域执行写操作。不然的话，当一个读线程在读取部分数据区域的时候被一个写线程抢占，之后再恢复运行，继续读取数据，读到的是更新的被更新后的数据，这样会产生混乱的，结果就是数据的不一致性。</p>
<p>　　下面看看使用读写锁用到的函数：</p>
<p>　　前两个函数是用来为读写锁初始化库的内部存储区域：</p>
<pre class="codesamp">int
pthread_rwlock_init (pthread_rwlock_t *<i class="var">lock</i>,
                     const pthread_rwlockattr_t *<i class="var">attr</i>);

int
pthread_rwlock_destroy (pthread_rwlock_t *<i class="var">lock</i>);</pre>
<p>　　pthread_rwlock_init()函数使用了lock参数，比按照attr所设定的属性初始化这个参数。在我们的例子中，我们会使用NULL作为属性，这样就按照默认属性初始化。</p>
<p>　　当使用完读写锁之后，就需要使用pthread_rwlock_destory()函数对其销毁。你不能够使用一个未初始化或已被销毁的读写锁。</p>
<p>　　之后，我们需要获取合适类型的锁。如上所述，有两种基本类型的锁：读操作需要非独占的访问、写操作需要独占的访问。为了函数名易懂，函数名都是按照锁的类型进行命名的：</p>
<pre class="codesamp">int
pthread_rwlock_rdlock (pthread_rwlock_t *<i class="var">lock</i>);

int
pthread_rwlock_tryrdlock (pthread_rwlock_t *<i class="var">lock</i>);

int
pthread_rwlock_wrlock (pthread_rwlock_t *<i class="var">lock</i>);

int
pthread_rwlock_trywrlock (pthread_rwlock_t *<i class="var">lock</i>);</pre>
<p>　　这里一共有四个函数，而不是预想的两个。两个预想的函数是pthread_rwlock_rdlock()与pthread_rwlock_wrlock()，这两个函数分别用于读与写。这些是阻塞函数，当锁对于所选操作不可用的时候，线程就会阻塞。当锁在适当情况下可用，线程就是解除阻塞。由于线程通过该函数解除阻塞，就可以假设访问锁所保护的资源是安全的。</p>
<p>　　有些时候，线程可能不想被阻塞，而是查看一下它是否能够获得那个锁。这就是那个“尝试(try)”版本函数的由来。需要注意的是尝试版的函数在能够获取锁的时候会获取这个锁，而在不能获取的时候它们也不会阻塞线程，它只会返回一个错误标识。它们在能够获取锁的时候会获取锁的原因是简单的。假设有线程要获取这个锁来执行读取操作，不过不想在锁不可用的时候阻塞。这个线程就会调用pthread_rwlock_tryrdlock()函数，之后它被告知可以获得这个锁。如果这时pthread_rwlock_tryrdlock()函数不分配这个锁，就可能有坏事发生——另外一个能够抢占这个线程的线程执行了，并且这第二个线程可能会用不兼容的模式锁定了资源。由于第一个线程实际上没有获取这个锁，当其确实开始获取锁的时候，它可能就会使用pthread_rwlock_rdlock()函数，这样的话它就会被阻塞，因为该资源在这种模式下已经不可用了。所以，如果在可以获取锁的时候我们没有获取，即使使用了try版的函数，最终也有可能被阻塞。</p>
<p>　　最后，不论锁是被如何使用的，我们都要释放它：</p>
<pre class="codesamp">int
pthread_rwlock_unlock (pthread_rwlock_t *<i class="var">lock</i>);</pre>
<p>　　一旦线程完成了对资源的操作之后，它必须通过调用这个函数释放锁。如果锁按照其他等待线程所需要的模式已经可用了，那么那个等待线程就会进入就绪状态。</p>
<p>　　需要注意我们不能够使用互斥体来完成这种类型的同步。互斥体是一个单线程的代理，这对于写操作是可以的，一旦涉及到读的情况就会完全失败，因为使用互斥体的话只能够允许一个线程执行读操作。而信号量也是不能使用的，因为使用它的话就不能分辨两者模式的访问了，信号量可以允许多个读线程的访问，不过当一个写线程试图获取它的时候，就和读线程获取它一样不能分辨出来，这样的话结果就是对一个资源可能会有多个读操作与一个或多个写操作同时存在，这是非常糟糕的！</p>


<p>Related posts:<ol><li><a href='http://www.speedvi.net/2010/02/23/208.html' rel='bookmark' title='用于线程同步的Sleepon锁'>用于线程同步的Sleepon锁</a> <small>　　在多线程程序中常遇到的另外一个情况就是让线程等待某件事的发生。这件事可以是任何事。它可以是设备上的数据就绪了，也可以是传送带到达了合适的位置或数据已经写入磁盘了，等等。另外还要讨论一下多个线程等待某个事件的情况。 　　为了实现这个功能，我们可以使用条件变量(condition variable)或是更简单的睡眠锁(sleepon lock)。 　　要使用睡眠锁，你需要执行几个操作。先看看要调用的函数，之后再看看你该如何使用这个锁： &nbsp; int pthread_sleepon_lock (void); int...</small></li>
<li><a href='http://www.speedvi.net/2010/02/24/211.html' rel='bookmark' title='用于线程同步的条件变量(Condition Variables)'>用于线程同步的条件变量(Condition Variables)</a> <small>　　条件变量(condition variables或condvars)与前面讲的睡眠锁(sleepon lock)非常类似。而实际上睡眠锁是在条件变量的基础上构建的，这也是为什么我们在睡眠锁的例子的解释表中有一个CONDVAR状态。它也能通过不停的调用pthread_cond_wait()函数来释放互斥体、等待以及重新获取互斥体，和pthread_sleepon_wait()函数一样。 　　下面我们就略过初始化的步骤，并使用条件变量来重新完成sleepon部分的那个生产者与消费者的多线程的程序。之后再讨论调用的函数。 &nbsp; /* * cp1.c */ #include...</small></li>
<li><a href='http://www.speedvi.net/2009/12/27/184.html' rel='bookmark' title='内核状态'>内核状态</a> <small>RUNNING 　　系统的运行状态简单的说就是表示有线程处于活动状态并在使用CPU。在多处理器系统中，可以有多个线程处于运行状态；在单处理器系统中，只能有一个线程处于运行状态。 READY 　　就绪状态表示线程现在可以运行了，只不过还未运行，因为现在有另外一个线程（同优先级或更高优先级）正在运行。如果有两个线程都有能力使用处理器，其中一个线程的优先级为10而另外一个线程的优先级为7，那么优先级为10的线程将进入运行状态而优先级为7的线程将进入就绪状态。 &nbsp; 阻塞状态(blocked states) 　　我们应该如何称呼阻塞状态？其实在系统中并不是只有一种阻塞状态，在实际系统中可以有数十种阻塞状态。 　　为什么会有这么多种呢？因为内核一直保留着线程为何被阻塞的原因。 　　在前面我们已经讲到了两种阻塞状态——当线程等待互斥体时被阻塞，这时线程就是MUTEX状态。当一个线程由于等待信号变量而被阻塞的话，这时线程就是SEM状态。这些状态简要的说明了线程是阻塞于哪个队列或资源。...</small></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.speedvi.net/2010/02/21/207.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

