<?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="https://blog.gmem.cc/tag/%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Sun, 19 Apr 2026 07:54:29 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Go语言并发编程</title>
		<link>https://blog.gmem.cc/go-concurrent-programming</link>
		<comments>https://blog.gmem.cc/go-concurrent-programming#comments</comments>
		<pubDate>Thu, 04 Feb 2016 09:08:02 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[并发编程]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=18001</guid>
		<description><![CDATA[<p>与传统语言不同，Go是为并发而设计的语言，它在语言层次上提供了对并发编程的大量支持。 Goroutine 注意点 修改多个Goroutine同时访问的数据时，必须串行化访问，方法有两种： 通过通道操作 使用sync、sync/atomic包提供的同步原语 在不改变语义的前提下，编译期和处理器可能对读写操作进行重新排序。由于重新排序的存在，不同Goroutine看到的执行顺序可能不同。 在多Goroutine访问共享变量的情况下，必须使用同步机制，确保读操作能看到期望的（happens before的）写。 Happens Before 初始化 Go应用初始化时只有一个Goroutine，它能够创建更多的Goroutine，这些Goroutine会并发的运行。 如果包p导入了包q，则q的init函数的执行完毕，发生在任何p的init函数之前。 协程创建 go语句的执行，发送在Goroutine主体逻辑执行之前。 协程销毁 Goroutine的销毁不保证发生在任何事件之前。 通道通信 向一个通道发送数据，发生在接收数据之前： [crayon-69e5e34414d30609705933/] 上面的例子会确保一定能打印hello, <a class="read-more" href="https://blog.gmem.cc/go-concurrent-programming">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/go-concurrent-programming">Go语言并发编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>与传统语言不同，Go是为并发而设计的语言，它在语言层次上提供了对并发编程的大量支持。</p>
<div class="blog_h1"><span class="graybg">Goroutine</span></div>
<div class="blog_h2"><span class="graybg">注意点</span></div>
<p>修改多个Goroutine同时访问的数据时，必须串行化访问，方法有两种：</p>
<ol>
<li>通过通道操作</li>
<li>使用sync、sync/atomic包提供的同步原语</li>
</ol>
<p>在不改变语义的前提下，编译期和处理器可能对读写操作进行重新排序。由于重新排序的存在，不同Goroutine看到的执行顺序可能不同。</p>
<p>在多Goroutine访问共享变量的情况下，必须使用同步机制，确保读操作能看到期望的（happens before的）写。</p>
<div class="blog_h2"><span class="graybg">Happens Before</span></div>
<div class="blog_h3"><span class="graybg">初始化</span></div>
<p>Go应用初始化时只有一个Goroutine，它能够创建更多的Goroutine，这些Goroutine会并发的运行。</p>
<p>如果包p导入了包q，则q的init函数的执行完毕，发生在任何p的init函数之前。</p>
<div class="blog_h3"><span class="graybg">协程创建</span></div>
<p>go语句的执行，发送在Goroutine主体逻辑执行之前。</p>
<div class="blog_h3"><span class="graybg">协程销毁</span></div>
<p>Goroutine的销毁不保证发生在任何事件之前。</p>
<div class="blog_h3"><span class="graybg">通道通信</span></div>
<p>向一个通道发送数据，发生在接收数据之前：</p>
<pre class="crayon-plain-tag">var c = make(chan int, 10)
var a string

func f() {
	a = "hello, world"
	c &lt;- 0
}

func main() {
	go f()
	&lt;-c
	print(a)
}</pre>
<p>上面的例子会确保一定能打印hello, world。 </p>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>Goroutine是由Go运行时管理的轻量级线程，不依赖于操作系统级别的线程机制。</p>
<p>下面是Goroutine简单的例子：</p>
<pre class="crayon-plain-tag">func worker(name string) {
    for i := 0; i &lt; 2; {
        time.Sleep(time.Second)
        fmt.Println("%v-%v", name, i)
        i++
    }
}
func main() {
    // 使用go关键字，后面跟着一个函数调用，即可发动一个Goroutine
    go worker("W")
    worker("M")
}</pre>
<p>Goroutine和程序主体部分在<span style="background-color: #c0c0c0;">同一地址空间运行</span>，因此它们共享变量时必须进行同步。sync包提供了相关的同步原语。</p>
<p>Go运行时为Goroutine实现线程的多路复用 —— 如果某个Goroutine阻塞（例如调用了某个阻塞性的系统调用），则运行时自动将<span style="background-color: #c0c0c0;">阻塞Goroutine所在的</span>OS线程上分配的<span style="background-color: #c0c0c0;">其余Goroutine</span>转移到其它<span style="background-color: #c0c0c0;">Runnable的</span>OS线程上。</p>
<p>每个Goroutine都需要栈资源，Go运行时支持动态栈大小，新创建的Goroutine的栈大小为几个KB，并根据需要进行扩展/收缩。这种动态栈大小让Goroutine平均资源消耗很小，在单台机器上创建10万级别的Goroutine很常见，但是创建相同数量的OS线程，负担要大得多。</p>
<div class="blog_h2"><span class="graybg">G-M-P模型</span></div>
<p>Goroutine的调度模型由Go运行时实现。调度模型发生过一次较大的重构。</p>
<p>G-M-P调度模型从Go 1.2版本引入，并沿用至今。在此模型中，有三个重要的角色：</p>
<ol>
<li>G：对应Go语言中的Goroutine，运行时需要维护每个Goroutine的<span style="background-color: #c0c0c0;">执行栈、执行状态</span>等信息</li>
<li>P：逻辑处理器，每个<span style="background-color: #c0c0c0;">逻辑处理器</span>对应一个<span style="background-color: #c0c0c0;">局部运行队列</span>，G可以在其上排队等候执行。P决定了Go程序中可以<span style="background-color: #c0c0c0;">并行执行的Goroutine的数量</span>（当然不能超过物理CPU的核心数）。<span style="background-color: #c0c0c0;">对于G来说，P就是运行它的CPU</span></li>
<li>M：对应操作系统线程。M不保存任何G的信息，这让G跨M调度成为可能</li>
</ol>
<p>P必须绑定到M（<span style="background-color: #c0c0c0;">逻辑处理器必须绑定到OS线程</span>）才能让本地运行队列中的G有机会运行。<span style="background-color: #c0c0c0;">当P绑定到M后会执行调度循环</span>，调度循环会：</p>
<ol>
<li>从P的本地运行队列中获取可运行的G</li>
<li>切换到G的执行栈</li>
<li>执行G的函数</li>
</ol>
<p><a href="https://cdn.gmem.cc/wp-content/uploads/2020/05/gmp.jpg"><img class=" wp-image-32355 aligncenter" src="https://cdn.gmem.cc/wp-content/uploads/2020/05/gmp.jpg" alt="gmp" width="706" height="530" /></a></p>
<div class="blog_h2"><span class="graybg">抢占式调度</span></div>
<p>如果G中执行了死循环，那么G将永远占据对应的P和M。这样会导致相同P中的其它G没有机会执行，更严重的是，如果只有一个P的情况下，程序中所有其它G都会被饿死。</p>
<p>为了解决上面的问题，Go 1.2引入抢占式调度。Go会<span style="background-color: #c0c0c0;">在每个函数的入口都添加一段额外的代码，以保证Go运行时有机会检查，并执行抢占式调度</span>。这种抢占式调度只能解决部分问题，对于没有任何函数调用的单纯死循环仍然无能为力。</p>
<p>Go程序启动时，运行时会创建一个名为sysmon的M（监控线程），此M不需要绑定P即可运行。sysmon每隔20us~10ms就会运行并执行对长时间运行的G发起抢占调度：</p>
<ol>
<li>设置G的抢占标识位为true</li>
<li>当G下一次调用任何函数时，Go运行时即执行抢占</li>
<li>G被放入P的本地运行队列，等待下一次调度</li>
</ol>
<div class="blog_h2"><span class="graybg">阻塞和调度</span></div>
<p>Go的理念是，基于阻塞性的接口编程，并使用Goroutine和Channel处理并发，而不是使用回调或者Future。对于Goroutine而言，所有IO操作都是阻塞性的。但是，<span style="background-color: #c0c0c0;">大部分IO操作都不会导致M阻塞</span>，这避免了大量创建操作系统线程。</p>
<div class="blog_h3"><span class="graybg">通道/网络阻塞</span></div>
<p>Go运行时实现了netpoller，它将操作系统提供的异步网络IO转换为Go中的阻塞性IO，这一方面防止M被阻塞，另一方面保持了Go语言内部的阻塞性接口风格。</p>
<p>当G在通道操作或者网络IO上阻塞时，运行时调度器将其放入某个等待队列，M会尝试执行下一个可运行的G。<span style="background-color: #c0c0c0;">如果没有可运行的G则M和P解绑并进入休眠状态</span>。</p>
<p>当通道操作完成或者网络IO可用时，等待队列中的G被唤醒，标记为可运行并放入到某个P的本地运行队列中，绑定M并执行。</p>
<div class="blog_h3"><span class="graybg">系统调用阻塞</span></div>
<p>对于普通文件（Non-Pollable）的IO操作会导致M阻塞，等待IO操作完成后被操作系统唤醒。这种情况下，P会和M分离，寻求其它的M进行绑定，如果没有空闲的M则会创建新的M。这意味着大量的IO<span style="background-color: #c0c0c0;">可能导致很多操作系统线程被创建</span>。</p>
<p>发生任何其它阻塞性系统调用时，Go运行时的调度逻辑类似。</p>
<div class="blog_h2"><span class="graybg">逻辑处理器</span></div>
<p>Go运行时会在逻辑处理器P上调度Goroutine，每个P会绑定一个操作系统线程M。每个P上可能有多个Goroutine等待被执行，这些Goroutine形成的队列被叫做本地运行队列。</p>
<p><span style="background-color: #c0c0c0;">新创建的Goroutine会被放置到一个全局运行队列中</span>，Go运行时的调度器会进行调度，把Goroutine分配给某个P。</p>
<p>调用<pre class="crayon-plain-tag">runtime.GOMAXPROCS(1)</pre>可以设置逻辑处理器的数量。逻辑处理器的数量应该和物理核心数量一致，设置的过大并不能提升性能：</p>
<pre class="crayon-plain-tag">runtime.GOMAXPROCS(runtime.NumCPU())</pre>
<p>GOMAXPROCS最大可以设置到256。</p>
<div class="blog_h2"><span class="graybg">调度器状态</span></div>
<p>设置环境变量GODEBUG可以查看运行时调度器的状态，例如设置<pre class="crayon-plain-tag">GODEBUG=schedtrace=1000</pre>则Go运行时每秒钟都会在控制台上打印调度器状态：</p>
<pre class="crayon-plain-tag">SCHED 10ms: gomaxprocs=8 idleprocs=6 threads=4 spinningthreads=1 idlethreads=1 runqueue=0 [1 0 0 0 0 0 0 0]

# SCHED，固定前缀，表示这一行调试信息由运行时调度器打印
# 10ms，从程序启动到打印信息经过的时间
# gomaxprocs 最大P数量
# idleprocs 空闲的P数量
# threads 操作系统线程数量
# spinningthreads 正在自旋的操作系统线程数量
# idlethread 空闲的操作系统线程数量
# runqueue 全局运行队列中G的数量
# [1 0 0 0 0 0 0 0] 各P的本地队列中G的数量</pre>
<p>设置环境变量<pre class="crayon-plain-tag">GODEBUG=schedtrace=1000,scheddetail=1</pre>可以获得更加详细的信息。 </p>
<div class="blog_h2"><span class="graybg">应用的退出</span></div>
<p>应用程序退出时，不会等待Goroutine执行完毕，而是会立即退出。解决办法包括：</p>
<ol>
<li>使用WaitGroup，让主Goroutine等待Worker Goroutine完成</li>
<li>如果Worker是长期运行的消息处理循环，应当从主Goroutine向其发送信号，导致其退出。最常见的用法是stop chan</li>
</ol>
<div class="blog_h1"><span class="graybg">通道</span></div>
<p>基于通信来共享内存，而不是基于共享内存进行通信 —— Share memory by communicating; don't communicate by sharing memory。</p>
<div class="blog_h2"><span class="graybg">通道简介</span></div>
<p>Goroutine和Channel受到CSP并发模型（Communicating Sequential Processes）的启发。在CSP中，多个Process使用Channel进行通信，并且这种通信通常是同步式的。</p>
<p>Go语言中的通道是一种强类型的管道，你可以使用通道操作符<pre class="crayon-plain-tag">&lt;-</pre>来从通道接收数据、发送数据到通道：</p>
<pre class="crayon-plain-tag">// 类似于切片，你需要先创建才能使用通道
ch := make(chan int)

ch &lt;- v      // 将值v发送到通道ch
v := &lt;-ch    // 从ch接收数据并赋值给v</pre>
<p>下面是两个Goroutine通过Channel进行通信的例子：</p>
<pre class="crayon-plain-tag">func receiver(ch chan int8) {
    for val := range ch {
        fmt.Printf("Received value: %v\n", val)
    }
}
func sender(ch chan int8) {
    for i := 0; i &lt; 3; i++ {
        ch &lt;- int8(i)
        time.Sleep(time.Second)
    }
    close(ch)
}
func main() {
    ch := make(chan int8)
    go sender(ch)
    go receiver(ch)
    time.Sleep(time.Second * 5)
}</pre>
<div class="blog_h2"><span class="graybg">通道阻塞</span></div>
<p>Goroutine可能会永久的阻塞在通道上，对于发送者来说，只要接收方没有响应，就会发生这种情况。</p>
<p>考虑下面的例子：</p>
<pre class="crayon-plain-tag">func First(query string, replicas ...Search) Result {  
    c := make(chan Result)
    searchReplica := func(i int) { c &lt;- replicas[i](query) }
    for i := range replicas {
        go searchReplica(i)
    }
    return &lt;-c
}</pre>
<p>此函数从一系列replicas中返回第一个replica(query)调用的结果。除了第一个执行完毕的Goroutine之外，其它的都会阻塞，原因是主Goroutine仅仅从通道获取一个数据后就退出了。</p>
<p>解决此问题的方法有多种：</p>
<ol>
<li>使用具有足够大缓冲的通道：<pre class="crayon-plain-tag">c := make(chan Result,len(replicas))</pre></li>
<li>使用带有default的select：<br />
<pre class="crayon-plain-tag">searchReplica := func(i int) {
    select {
    case c &lt;- replicas[i](query):
    // 如果通道c不可写，则执行下面的分支
    default:
    }
} </pre>
</li>
<li>使用一个“取消”通道：<br />
<pre class="crayon-plain-tag">done := make(chan struct{})
// 外层函数退出时，通道被关闭
defer close(done)

searchReplica := func(i int) {
    select {
    case c &lt;- replicas[i](query):
    // 那时任何协程都可以读取通道并退出
    case &lt;- done:
    }
} </pre>
</li>
</ol>
<div class="blog_h2"><span class="graybg">缓冲通道</span></div>
<p>默认情况下，通过通道发送、接收数据都会阻塞，直到通道的另外一端响应。 这样，Goroutine不需要明确的锁定或者条件变量即可同步。</p>
<p>你可以创建具有缓冲区的通道 —— 发送操作仅仅在缓冲区满的情况下阻塞，接收操作仅仅在缓冲区为空的情况下阻塞：</p>
<pre class="crayon-plain-tag">// 第二个参数为缓冲区大小
ch := make(chan int, 100)</pre>
<div class="blog_h2"><span class="graybg">关闭通道</span></div>
<p>你可以调用<pre class="crayon-plain-tag">close(chan)</pre>函数提示通道不再有更多的值可以被接收，通道的接收者可以为接收表达式赋第二个值，以测试通道是否关闭：</p>
<pre class="crayon-plain-tag">// 如果通道没有更多的值可以接收，并且已经针对通道调用过close，则ok = false
v, ok := &lt;-ch</pre>
<p>注意：</p>
<ol>
<li>仅仅<span style="background-color: #c0c0c0;">应当由发送者关闭通道</span>，而不是消费者。如果尝试<span style="background-color: #c0c0c0;">向已经关闭的通道发送数据，会导致Panic</span></li>
<li>通道不同于文件，通常你不需要显式关闭通道。除非你需要显式的告知接收者（例如提示其结束for...range循环）</li>
<li>从已经关闭的通道接收是安全的，第二返回值将为false</li>
</ol>
<div class="blog_h2"><span class="graybg">for...range</span></div>
<p><pre class="crayon-plain-tag">for i := range c</pre>格式的循环用于<span style="background-color: #c0c0c0;">遍历通道，直到通道被关闭</span>。下面这个例子中，子Goroutine产生斐波纳契数列，主Goroutine使用for...range循环遍历其输出：</p>
<pre class="crayon-plain-tag">func fibonacci(n int, c chan uint64) {
    x, y := uint64(0), uint64(1)
    for i := 0; i &lt; n; i++ {
        c &lt;- x
        x, y = y, x+y
    }
    close(c)
}

func main() {
    c := make(chan uint64, 50)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Printf("%v ", i)
    }
}</pre>
<p><span style="background-color: #c0c0c0;">多个Goroutine同时遍历单个通道是允许的</span>，不会出现竞态条件。但是要注意：</p>
<ol>
<li>尽量使用参数在Goroutine之间传递chan，而不是使用全局变量</li>
<li>对于<span style="background-color: #c0c0c0;">单个Goroutine来说，不要又读又写，容易死锁</span></li>
<li>如果使用不带缓冲的Chan不会出现死锁，那么使用带缓冲的Chan也不会出现死锁。反之不成立。可以先使用不带缓冲的Chan，当需要性能提升时，增加缓冲</li>
</ol>
<div class="blog_h2"><span class="graybg">通道的通道</span></div>
<p>Go中的通道是一等公民，可以像其它值一样被传来传去。甚至，你可以通过通道来传递另外一个通道。下面是一个远程计算服务的例子：</p>
<pre class="crayon-plain-tag">// 远程计算请求
type Request struct {
    // 计算参数
    args []int
    // 计算逻辑
    f func([]int) int
    // 处理结果返回的通道
    resultChan chan int
}

// 客户端
func Client(queue chan *Request) {

    request := &amp;Request{[]int{1, 2, 3, 4, 5}, func(a []int) (s int) {
        for _, v := range a {
            s += v
        }
        return
    }, make(chan int)}
    queue &lt;- request
    fmt.Printf("Result from server: %v", &lt;-request.resultChan)
}

// 远程计算服务
func Server(queue chan *Request) {
    for req := range queue {
        req.resultChan &lt;- req.f(req.args)
    }
}
func main() {
    queue := make(chan *Request)
    go Server(queue)
    go Client(queue)
    time.Sleep(time.Second)
}</pre>
<p> 可以看到，客户端通过通道发送请求，请求对象中包含了一个通道，服务器向此通道发送计算结果。</p>
<div class="blog_h2"><span class="graybg">nil通道</span></div>
<p>在零值通道上发送、接收都会永久阻塞。</p>
<div class="blog_h2"><span class="graybg">单向通道</span></div>
<pre class="crayon-plain-tag">var send chan&lt;- int      // 在chan关键字后侧加&lt;-表示仅仅能发送
var receive &lt;-chan int   // 在chan关键字左侧加&lt;-表示仅仅能接收

// 单向通道常常用于函数或方法的参数中
func counter(out chan&lt;- int) {
}</pre>
<div class="blog_h2"><span class="graybg">select</span></div>
<p>select...case语句允许Goroutine在多个通信操作符上等待，当<span style="background-color: #c0c0c0;">至少有一个操作符（可以在default分支中立即退出）准备好（可读或可写）</span>，<span style="background-color: #c0c0c0;">Select才从阻塞中恢复</span>。如果同时有多个操作符准备好，Select会随机执行其中一个分支： </p>
<pre class="crayon-plain-tag">func fibonacci(c chan uint64, quit chan bool) {
    x, y := uint64(0), uint64(1)
    for {
        select {
        // 发送端
        case c &lt;- x:
            x, y = y, x+y
            // 这里的break用于终止case执行，如果要跳出for，可以使用标签或者goto
            break
        // 接收端，注意接收数据后可不赋值给任何变量
        case &lt;-quit:
            fmt.Println("Quit signaled")
            return
        // 如果没有任何case满足条件，则执行default
        default:
            // 可以在此休眠，执行阻塞性收发，等等
        }
    }
}

func main() {
    c := make(chan uint64)
    q := make(chan bool)
    go fibonacci(c, q)
    // 从匿名函数启动Groutine
    go func() {
        for i := 0; i &lt; 50; i++ {
            fmt.Println(&lt;-c)
        }
        q &lt;- true
    }()
    time.Sleep(time.Second)
}</pre>
<p>下面是Istio代码中的一个示例：</p>
<pre class="crayon-plain-tag">select {
case client.pushChannel &lt;- &amp;XdsEvent{}:
    // 意味着推送通道可用，事件推送成功
case &lt;-client.doneChannel:
    // 意味着客户端已经关闭连接
case &lt;-time.After(PushTimeout):
    // 意味着在超时之前，推送通道不可用，客户端也没关闭。可能是网络慢卡住了
}</pre>
<p>感受一下，用非常简洁的代码就把正常、异常情况处理好了。 </p>
<div class="blog_h2"><span class="graybg">通道用法示例</span></div>
<div class="blog_h3"><span class="graybg">定时器</span></div>
<pre class="crayon-plain-tag">// Ticker会定期向通道写入数据
tickChan := time.NewTicker(c.flaggerWindow).C
for {
	select {
        // 定期触发这个case
	case &lt;-tickChan:
		c.scheduleCanaries()
	case &lt;-stopCh:
		return nil
	}
}</pre>
<div class="blog_h3"><span class="graybg">等待子例程</span></div>
<pre class="crayon-plain-tag">c := make(chan int)  // 分配一个通道
// 在Go程中启动排序。当它完成后，在通道上发送信号
go func() {
	list.Sort()
	c &lt;- 1  // 发送信号，什么值无所谓
}()
doSomethingForAWhile()
&lt;-c   // 等待排序结束，丢弃发来的值</pre>
<div class="blog_h3"><span class="graybg">信号量</span></div>
<p>还有缓冲的通道可以作为信号量使用：</p>
<pre class="crayon-plain-tag">var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
	sem &lt;- 1 // 等待活动队列清空。
	process(r)  // 可能需要很长时间
	&lt;-sem    // 完成；使下一个请求可以运行
}

func Serve(queue chan *Request) {
	for {
		req := &lt;-queue
		go handle(req)
	}
} </pre>
<div class="blog_h1"><span class="graybg">锁</span></div>
<div class="blog_h2"><span class="graybg">Mutex</span></div>
<p>通道是跨Goroutine通信的利器，但是某些时候Goroutine之间不需要传递数据，我们只需要保证在任何时刻，仅仅它们其中的一个能够访问（同步）某项资源以避免冲突，这时你可以使用互斥量。</p>
<div class="blog_h3"><span class="graybg">竞态条件</span></div>
<p>发生Goroutine之间的竞态条件和传统的基于线程的语言类似：</p>
<pre class="crayon-plain-tag">var count uint32 = 0
var wg sync.WaitGroup

func inc() {
    defer wg.Done()
    count++     // 非原子操作
}
func main() {
    const N = 500
    wg.Add(N)
    for i := 0; i &lt; N; i++ {
        go inc()
    }
    wg.Wait()
    fmt.Printf("Expected: %v, Actual: %v", N, count) // Expected: 500, Actual: 488
}</pre>
<p>运行上段程序，可以看到计数器的结果不符合预期，每次都会不一样。这是由于对共享变量count的++操作不是原子操作，不受保护的访问会导致写覆盖。</p>
<p>现实中的例子要复杂的多，可能很难察觉到问题。你可以为go build 传入 --race参数，这样，在运行代码时，Go会自动检测竞态条件：</p>
<pre class="crayon-plain-tag">==================
WARNING: DATA RACE
Read at 0x0000005bddb0 by goroutine 7:
  main.inc()
      /home/alex/Go/projects/go-study/application.go:13 +0x69

Previous write at 0x0000005bddb0 by goroutine 6:
  main.inc()
      /home/alex/Go/projects/go-study/application.go:13 +0x83

Goroutine 7 (running) created at:
  main.main()
      /home/alex/Go/projects/go-study/application.go:19 +0x6e

Goroutine 6 (finished) created at:
  main.main()
      /home/alex/Go/projects/go-study/application.go:19 +0x6e
==================
Found 1 data race(s)</pre>
<div class="blog_h3"><span class="graybg">使用互斥锁</span></div>
<p> Go标准库中定义的<pre class="crayon-plain-tag">sync.Mutex</pre>，实现了如下接口：</p>
<pre class="crayon-plain-tag">type Locker interface {
    // 调用此方法可以锁定共享资源
    Lock()
    // 调用此方法可以解除对共享资源的锁定
    Unlock()
}</pre>
<p>使用互斥锁来改造上面的计数器代码：</p>
<pre class="crayon-plain-tag">var lock sync.Mutex

func inc() {
    defer wg.Done()
    defer lock.Unlock()
    lock.Lock()
    count++
}</pre>
<p>即可同步化对共享变量count的访问。下面是另外一个使用互斥锁的例子：</p>
<pre class="crayon-plain-tag">import (
    "fmt"
    "sync"
    "time"
)

// 并发安全的计数器
type SafeCounter struct {
    v map[string]int
    // 锁
    mux sync.Mutex
}

//  为某个键进行计数
func (c *SafeCounter) Inc(key string) {
    // 在进行不安全操作前后进行加锁、解锁
    c.mux.Lock()
    c.v[key]++
    c.mux.Unlock()
}

func (c *SafeCounter) Value(key string) int {
    c.mux.Lock()
    // 可以使用defer，确保此方法退出时会执行解锁
    defer c.mux.Unlock()
    return c.v[key]
}

func main() {
    c := SafeCounter{v: make(map[string]int)}
    const key = "default-counter"
    for i := 0; i &lt; 10000000; i++ {
        go c.Inc(key)
    }

    time.Sleep(time.Second)
    fmt.Println(c.Value(key))
} </pre>
<div class="blog_h2"><span class="graybg">RWMutex</span></div>
<p>互斥锁要求所有访问共享资源的Goroutine串行化执行，因此性能较差。在读多写少的情况下，使用读写锁可以提升性能，读写锁的特点是：</p>
<ol>
<li>读锁不排斥其它的读锁，但是排除任何写锁</li>
<li>写锁排除任何锁</li>
</ol>
<pre class="crayon-plain-tag">var rw sync.RWMutex

// 加读锁
rw.RLock()
// 解读锁
rw.RUnlock()

// 加写锁
rw.Lock()
// 解写锁
rw.Unlock()</pre>
<div class="blog_h1"><span class="graybg">原子操作</span></div>
<p>在Go语言中，<span style="background-color: #c0c0c0;">唯一被保证是原子性的</span>，就是sync.atomic中定义的那些操作。也就是说，哪怕是简单的赋值，也需要保护：</p>
<pre class="crayon-plain-tag">clearCacheMutex.Lock()
clearCacheTimerSet = false
clearCacheMutex.Unlock()</pre>
<p>使用互斥量是最常见的保证原子性的方法，sync.atomic包相对不常用。</p>
<div class="blog_h2"><span class="graybg">sync.atomic</span></div>
<p>此包提供了很多低级的原子性内存访问原语，可以用于实现同步算法。 使用此包时要想当小心，推荐优先基于通道或者锁实现同步。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>AddInt32</td>
<td>
<p><pre class="crayon-plain-tag">func AddInt32(addr *int32, delta int32) (new int32)</pre></p>
<p>原子的给addr的值加delta，并返回返回新值</p>
<p>类似的函数包括AddInt64、AddUint32、AddUintptr等</p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>CompareAndSwapInt32</td>
<td>
<p><pre class="crayon-plain-tag">CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)</pre></p>
<p> 针对int32针对CAS算法，示例：</p>
<pre class="crayon-plain-tag">lock := int32(0)
// 如果旧值和新值不同，则原子的设置为新值
if atomic.CompareAndSwapInt32(&amp;lock, 0, 1) {
    fmt.Printf("Locked: %v", lock) // Locked: 1
}</pre>
<p> 类似的函数包括CompareAndSwapInt64、CompareAndSwapPointer等</p>
</td>
</tr>
<tr>
<td>LoadInt32</td>
<td>原子的加载值</td>
</tr>
<tr>
<td>StoreInt32</td>
<td>原子的保存值</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">并发控制</span></div>
<div class="blog_h2"><span class="graybg">WaitGroup</span></div>
<p>等待组类似于Java的CountdownLatch：</p>
<pre class="crayon-plain-tag">var wg sync.WaitGroup
const N = 100
wg.Add(N) // 需要N次Done调用才能解除Wait()
for i := 0; i &lt; N; i++ {
    go func() {
        wg.Done()
        wg.Wait()  // 等待所有协作者完成
    }()
}
wg.Done()
wg.Wait()  // 等待所有协作者完成</pre>
<p>注意：<span style="background-color: #c0c0c0;">传递WaitGroup时应当传递其指针</span>。</p>
<div class="blog_h2"><span class="graybg">Context</span></div>
<p>使用Context可以方便的控制多个相互协作的Goroutine，下面是一个简单的例子：</p>
<pre class="crayon-plain-tag">import (
    "context"
    "log"
    "math/rand"
    "time"
)

func main() {
    // 使用空白上下文为根，产生一个子上下文
    // 当返回的cancel函数被调用后，子上下文的Done通道自动关闭
    ctx, cancel := context.WithCancel(context.Background())

    for i := 0; i &lt; 5; i++ {
        go genRandNum(ctx)
    }
    // 让Goroutine工作一会儿
    time.Sleep(time.Second * 3)
    cancel() // 发出取消上下文的信号
    time.Sleep(time.Second * 3)
}
func genRandNum(ctx context.Context) {
    for {
        select {
        // 检测上下文是否取消
        case &lt;-ctx.Done():
            // 如果上下文已经取消，则退出Goroutine
            log.Println("Stop random number generation.")
            return
        default:
            // 如果上下文尚未取消，则产生随机数
            log.Printf("Generated number: %v", rand.Int())
            time.Sleep(time.Second)
        }
    }
}</pre>
<p>context.WithCancel()、context.WithTimeout()调用返回的第二个值，即<span style="background-color: #c0c0c0;">cancel()函数，其作用是触发ctx.Done()通道可读（关闭）</span>。</p>
<div class="blog_h3"><span class="graybg">Context接口 </span></div>
<p>Context接口定义了四个方法：</p>
<pre class="crayon-plain-tag">type Context interface {
    // 返回设置的最后期限，如果没有设置最后期限，第二个返回参数为false
    Deadline() (deadline time.Time, ok bool)
    // 返回一个只读的通道，如果此通道可读取，则意味着父Context已经发起了取消请求
    // 从该通道读取到东西后，当前Goroutine应该进行资源清理并退出
    Done() &lt;-chan struct{}
    // 返回上下文被取消的原因
    Err() error
    // 返回上下文上绑定的属性
    Value(key interface{}) interface{}
}</pre>
<p>内置的实现包括：</p>
<pre class="crayon-plain-tag">var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)
func Background() Context {
    return background
}
func TODO() Context {
    return todo
}</pre>
<p>background、todo都是<span style="color: #444444;">emptyCtx类型，这是一个不可取消、没有设置最后期限、没有绑定任何属性的Context。</span></p>
<p>context.Background()通常在main函数中用于根Context。 </p>
<div class="blog_h3"><span class="graybg">上下文树</span></div>
<p>emptyCtx不能做任何事情，我们通常将其作为根Context，并利用下面的函数来构建上下文树：</p>
<pre class="crayon-plain-tag">// 产生一个子上下文和取消函数，调用函数可以取消上下文
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// 产生一个子上下文，在到达最后期限时，会自动取消，也可以手工取消
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
// 类似上面
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
// 通过上下文共享数据
func WithValue(parent Context, key, val interface{}) Context</pre>
<p>注意：调用<span style="background-color: #c0c0c0;">取消函数会导致当前上下文以及任何后代上下文被取消</span>，也就是Done()关闭</p>
<div class="blog_h3"><span class="graybg">注意点</span></div>
<ol>
<li>以参数的方式传递Context，并且不会将其包装在结构中</li>
<li><span style="color: #444444;">Context作为第一个参数</span></li>
<li>不要传递nil Context</li>
<li>避免通过Context传递不必要的数据</li>
<li>Context可以跨Goroutine安全访问</li>
</ol>
<div class="blog_h1"><span class="graybg">协程调度</span></div>
<p>调度器会在<span style="background-color: #c0c0c0;">GC、go语句、阻塞channel操作、阻塞系统调用和lock操作</span>后运行。它也会在<span style="background-color: #c0c0c0;">非内联函数调用</span>后执行。</p>
<p>你也可以显式的唤起调度器：</p>
<pre class="crayon-plain-tag">for !done {
    runtime.Gosched()
}</pre>
<p>但是一个空循环，就可以阻止调度器运作。 </p>
<div class="blog_h1"><span class="graybg">协程池</span></div>
<p>使用Go语言实现一个线程池是很简单的。下面分析Istio源码中包含的线程池的例子。</p>
<div class="blog_h2"><span class="graybg">Istio协程池</span></div>
<div class="blog_h3"><span class="graybg">代码</span></div>
<pre class="crayon-plain-tag">package pool

import (
	"sync"
)

// 代表工作协程需要执行的函数
type WorkFunc func(param interface{})

// 协程池结构，持有一组可重用的协程，工作函数可以调度到协程上
type GoroutinePool struct {
	queue          chan work      // 工作队列，缓冲通道
	wg             sync.WaitGroup // 用于在所有工作完成之前阻止shutdown操作
	singleThreaded bool           // 提示是否运行在单线程模式
}

// 放入队列的工作，即工作函数+参数
type work struct {
	fn    WorkFunc
	param interface{}
}

// 创建新的协程池
func NewGoroutinePool(queueDepth int, singleThreaded bool) *GoroutinePool {
	gp := &amp;GoroutinePool{
		queue:          make(chan work, queueDepth), // 队列深度即通道缓冲大小
		singleThreaded: singleThreaded,
	}
// 至少添加一个工作协程
	gp.AddWorkers(1)
	return gp
}

// 协程池实现io.Closer接口。在关闭时，必须在等待组上等待 —— 等待所有工作协程退出
func (gp *GoroutinePool) Close() error {
	if !gp.singleThreaded {
// 关闭通道
		close(gp.queue)
// 等待
		gp.wg.Wait()
	}
	return nil
}

// 调度一份工作，在未来执行
// 调用者必须确保，工作函数仅仅依赖于通过params传递的“外部数据” —— 要保证这一点，你可以
// 传递一个普通命名函数给此方法，而不是一个内联的匿名函数（容易形成闭包）
func (gp *GoroutinePool) ScheduleWork(fn WorkFunc, param interface{}) {
	if gp.singleThreaded {
// 单线程模式下，执行运行
		fn(param)
	} else {
// 否则，写入到通道。如果通道满了，可能会导致调用者阻塞
		gp.queue &lt;- work{fn: fn, param: param}
	}
}

// 添加工作协程到池中，增加并行度
func (gp *GoroutinePool) AddWorkers(numWorkers int) {
	if !gp.singleThreaded {
// 增加需要等待的数量
		gp.wg.Add(numWorkers)
		for i := 0; i &lt; numWorkers; i++ {
// 工作协程的工作循环
			go func() {
// 取出一个工作并执行
				for work := range gp.queue {
					work.fn(work.param)
				}
// 如果通道关闭，则减少等待组计数。所有协程都执行到这一步后，Close()调用才能从阻塞中返回
				gp.wg.Done()
			}()
		}
	}
}</pre>
<div class="blog_h3"><span class="graybg">测试</span></div>
<pre class="crayon-plain-tag">package pool

import (
	"sync"
	"testing"
)

func TestWorkerPool(t *testing.T) {
	const numWorkers = 123
	const numWorkItems = 456

	parameterMismatch := false

	for i := 0; i &lt; 2; i++ {
		gp := NewGoroutinePool(128, i == 0)
		gp.AddWorkers(numWorkers)

		wg := &amp;sync.WaitGroup{}
		wg.Add(numWorkItems)

		for i := 0; i &lt; numWorkItems; i++ {
			passedParam := i // 在栈上捕获变量，避免创建闭包
			gp.ScheduleWork(func(param interface{}) { // 调度
				paramI := param.(int)
				if paramI != passedParam {
					parameterMismatch = true
				}
				wg.Done()
			}, passedParam)
		}

		// 等待所有工作执行完毕
		wg.Wait()

		if parameterMismatch {
			t.Fatal("Passed parameter was not as expected")
		}

		// 关闭池
		_ = gp.Close()
	}
}</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/go-concurrent-programming">Go语言并发编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/go-concurrent-programming/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Python并发编程</title>
		<link>https://blog.gmem.cc/python-concurrent-programming</link>
		<comments>https://blog.gmem.cc/python-concurrent-programming#comments</comments>
		<pubDate>Wed, 04 May 2011 07:58:48 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[并发编程]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4458</guid>
		<description><![CDATA[<p>基本概念 一个运行的程序称作进程。每个进程都有自己的系统状态，包括内存、已打开文件列表、用于跟踪正在执行的指令的程序计数器以及用于保存函数的局部变量的调用栈。通常在一个控制流序列中， 进程逐条执行语句，这一般称为进程的主线程。在任何一个给定的时刻，程序都只做一件事情。 程序可以使用库函数创建新的进程，比如os或subprocess模块中的函数（例如os.fork()、 subprocess.Popen等)。这些叫做子进程的进程是作为完全独立的实体运行的。 每个子进程都有自己的私有系统状态和执行主线程。因为子进程是独立的，所以它可以与原始进程并发执行。也就是说，创建子进程的进程可以继续处理别的事情，同时子进程在后台执行它自己的任务。 尽管进程是孤立的，但它们可以彼此通信，这称为进程间通信（Interprocess Communication, IPC)。 进程间通信的最常见形式是基于消息传递。一条消息就是一块原始字节的缓存。然后，就可以使用像 send()和recv()这样的简单操作，通过一个I/O通道（如管道或网络套接字）来传输或接收消息。另一种不太常见的IPC机制依赖于内存映射区域（参见nmap模块)。借助内存映射，进程可以创建共享的内存区域。如果对这些内存区域进行修改，那么査看这些区域的所有进程都能看到这些修改。 如果需要同时处理多个任务，可以在一个应用程序内使用多个进程，每个进程负责处理一部分工 作。 另一种将工作细分为多个任务的方法是使用线程。线程类似于进程，也具有自己的控制流和执行栈。但线程运行在创建它的进程内部，它们共享所有的数据和系统资源。当应用程序需要并发执行多个任务时，可以使用线程，但可能存在大量需要在各个任务之间共享的系统状态。 使用多个进程或线程时，主机操作系统负责安排它们的工作。安排的具体做法是：给每个进程（线程）分配一个小的时间片，并在所有活动任务之间快速循环——给每个任务分配一部分可用的CPU周期。例如，如果系统同时运行10个活动的进程，操作系统将给每个进程分配大约1/10的CPU时间，同时在进程之间快速循环。在具有多个CPU核心的系统上，操作系统在安排进程时可以尽可能使用每个 CPU，从而并行执行进程。 编写使用并发执行的程序原本就很复杂，其复杂性的一个主要原因就是同步和访问共享数据。也就是说，多个任务同时更新一个数据结构可能导致数据损坏和程序状态不一致（这个问题的正式说法是竞争条件）。要解决这些问题，并发程序必须找出关键的代码段，并使用互斥锁和其他类似的同步手段保护它们。例如，如果不同的线程尝试同时向一个文件写入数据，可以使用互斥锁来同步它们的 Python中的并发编程 在大多数系统上，Python同时支持消息传递和基于线程的并发编程。Python线程受到的限制有很多，Python解释器使用了内部的GIL (Global Interpreter Lock，全局解释器锁定），在任意指定的时刻只允许单个 <a class="read-more" href="https://blog.gmem.cc/python-concurrent-programming">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-concurrent-programming">Python并发编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基本概念</span></div>
<p>一个<span style="background-color: #c0c0c0;">运行的程序称作进程</span>。每个进程都有自己的<span style="background-color: #c0c0c0;">系统状态，包括内存、已打开文件列表、用于跟踪正在执行的指令的程序计数器以及用于保存函数的局部变量的调用栈</span>。通常在一个控制流序列中， 进程逐条执行语句，这一般称为进程的主线程。在<span style="background-color: #c0c0c0;">任何一个给定的时刻，程序都只做一件事情</span>。 程序可以使用库函数<span style="background-color: #c0c0c0;">创建新的进程，比如os或subprocess模块中的函数（例如os.fork()、 subprocess.Popen等)</span>。这些叫做<span style="background-color: #c0c0c0;">子进程</span>的进程是作为<span style="background-color: #c0c0c0;">完全独立的实体运行的。</span> 每个子进程都有自己的私有系统状态和执行主线程。因为子进程是独立的，所以它可以与原始进程并发执行。也就是说，创建子进程的进程可以继续处理别的事情，同时子进程在后台执行它自己的任务。</p>
<p>尽管进程是孤立的，但它们可以彼此通信，这称为<span style="background-color: #c0c0c0;">进程间通信（Interprocess Communication, IPC)</span>。 进程间通信的<span style="background-color: #c0c0c0;">最常见形式是基于消息传递</span>。一条消息就是一块原始字节的缓存。然后，就可以<span style="background-color: #c0c0c0;">使用像 send()和recv()这样的简单操作，通过一个I/O通道（如管道或网络套接字）来传输或接收消息</span>。另一种不太常见的IPC机制依赖于内存映射区域（参见nmap模块)。<span style="background-color: #c0c0c0;">借助内存映射，进程可以创建共享的内存区域</span>。如果对这些内存区域进行修改，那么査看这些区域的所有进程都能看到这些修改。 如果需要同时处理多个任务，可以在一个应用程序内使用多个进程，每个进程负责处理一部分工 作。</p>
<p>另一种将工作细分为多个任务的方法是使用线程。线程类似于进程，也具有自己的控制流和执行栈。但<span style="background-color: #c0c0c0;">线程运行在创建它的进程内部，它们共享所有的数据和系统资源</span>。当应用程序需要并发执行多个任务时，可以使用线程，但可能存在大量需要在各个任务之间共享的系统状态。</p>
<p>使用多个进程或线程时，主机操作系统负责安排它们的工作。安排的具体做法是：<span style="background-color: #c0c0c0;">给每个进程（线程）分配一个小的时间片，并在所有活动任务之间快速循环</span>——给每个任务分配一部分可用的CPU周期。例如，如果系统同时运行10个活动的进程，操作系统将给每个进程分配大约1/10的CPU时间，同时在进程之间快速循环。在具有多个CPU核心的系统上，操作系统在安排进程时可以尽可能使用每个 CPU，从而并行执行进程。 编写使用并发执行的程序原本就很复杂，其复杂性的一个主要原因就是同步和访问共享数据。也就是说，<span style="background-color: #c0c0c0;">多个任务同时更新一个数据结构可能导致数据损坏和程序状态不一致（这个问题的正式说法是竞争条件）</span>。要解决这些问题，并发程序必须找出关键的代码段，并使用<span style="background-color: #c0c0c0;">互斥锁和其他类似的同步手段保护它们</span>。例如，如果不同的线程尝试同时向一个文件写入数据，可以使用互斥锁来同步它们的</p>
<div class="blog_h1"><span class="graybg">Python中的并发编程</span></div>
<p>在大多数系统上，Python同时支持消息传递和基于线程的并发编程。Python线程受到的限制有很多，Python解释器使用了内部的<span style="background-color: #c0c0c0;">GIL (Global Interpreter Lock，全局解释器锁定），在任意指定的时刻只允许单个 Python线程执行</span>。无论系统上存在多少个可用的CPU核心，这限制了 <span style="background-color: #c0c0c0;">Python程序只能在一个处理器上 运行</span>。如果一个应用程序的<span style="background-color: #c0c0c0;">大部分是I/O受限（bounded）的，那么使用线程一般没有问题</span>，因为<span style="background-color: #c0c0c0;">额外的处理器对于花费大多数时间等待事件的程序帮助不大</span>。<span style="background-color: #c0c0c0;">对于涉及大量CPU处理的应用程序而言，使用线程来细分工作没有任何好处</span>，反而还会降低程序的运行速度。</p>
<p>避免大量使用线程的较为常见的做法是把这类应用程序<span style="background-color: #c0c0c0;">重新构造为异步事件处理系统</span>。例如，<span style="background-color: #c0c0c0;">中心的事件循环可以监控使用select模块的所有I/O,并将异步事件分离给大量I/O 处理器</span>。这是诸如<span style="background-color: #c0c0c0;">asyncore</span>这样的库模块和诸如<span style="background-color: #c0c0c0;">Twisted</span>等流行的第三方模块的基础。</p>
<p>如果要在Python中进行各种类型的并发编程，<span style="background-color: #c0c0c0;">消息传递很可能是必须熟练掌握的概念</span>。 即便使用线程，常推荐的方法也是将应用程序的结构设计为<span style="background-color: #c0c0c0;">大量独立的线程集合，这些线程通过消息队列交换数据</span>。这种特别的方法往往很少出错，因为它极大地<span style="background-color: #c0c0c0;">减少了对使用锁定和其他同步手段的需求</span>。<span style="background-color: #c0c0c0;">消息传递还会自然扩展到网络和分布式系统中</span>。消息传递的抽象也与髙级Python功能（如协程）有关。例如，协程是可以接收并处 理发送给它的消息的函数。因此，掌握了消息传递之后，写出的程序会较之前更灵活。</p>
<div class="blog_h1"><span class="graybg">multiprocessing模块</span></div>
<p>该模块为<span style="background-color: #c0c0c0;">在子进程中运行任务、通信和共享数据，以及执行各种形式的同步提供支持</span>。接口风格与threading模块类似，但要注意进程之间没有任何共享状态。</p>
<div class="blog_h2"><span class="graybg">多进程处理的一般建议</span></div>
<ol>
<li>确保进程之间传递的所有数据都能够序列化</li>
<li>避免使用共享数据，尽可能使用消息传递和队列。使用消息传递时，不必过于担心同步、锁定和其他问题。当进程的数量增长时，它往往还能提供更好的扩展</li>
<li>在必须行在单独进程中的函数内部，不要使用全局变量而应当显式地传递参数</li>
<li>尽量不要在同一个程序中混合使用线程和多线程处理</li>
<li>特別要注意关闭进程的方式。一般而言，需要显式地关闭进程，并使用一种定义良好的终止模式，而不要仅仅依赖于垃圾收集或者被迫使用terminate ()操作强制终止子进程</li>
<li>管理器和代理的使用与分布式计算中的多个概念密切相关（例如，分布式对象)</li>
<li>尽管此模块可以工作在Windows上，但还是应该仔细阅读官方文档中的各种微妙细节</li>
<li>最重要的一点是：尽量让事情变得简单</li>
</ol>
<div class="blog_h2"><span class="graybg">进程管理</span></div>
<p>进程是multiprocessing模块的核心，其构造函数如下：</p>
<pre class="crayon-plain-tag">Process([group [, target [, name [, args [, kwargs ]]]]])
# group 预留参数
# target 进程启动时需要执行的可调用对象
# name  进程名称的描述性字符串
# args 传递给target的位置参数的元组
# kwargs 传递个target的关键字参数的字典</pre>
<p>Process具有以下实例方法、属性：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 方法/属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>is_alive()</td>
<td>如果进程仍在运行，返回True</td>
</tr>
<tr>
<td>join([timeout])</td>
<td>等待该进程停止，最大timeout</td>
</tr>
<tr>
<td>run()</td>
<td>继承Process并改写run()方法，其效果与传递target一样</td>
</tr>
<tr>
<td>start()</td>
<td>启动进程</td>
</tr>
<tr>
<td>terminate()</td>
<td>强制终止进程，不会进行任何清理操作。如果进程创建了子进程，子进程变为僵尸进程；如果进程持有锁定则可能导致死锁</td>
</tr>
<tr>
<td>autokey</td>
<td>进程的身份验证键。除非显式设定，这是由os.urandom()函数生成的32字符的字符串</td>
</tr>
<tr>
<td>daemon</td>
<td>
<p>一个Boolean标志，指示进程是否是后台进程。当创建它的Python进程终止时，后台（Daemonic）进程将自动终止。另外，禁止后台进程创建自己的新进程。daemon的值必须在使用start()函数 启动进程之前进行设置</p>
</td>
</tr>
<tr>
<td>exitcode</td>
<td>
<p>进程的整数退出代码。如果进程仍然在运行，它的值为None。如果值为负数，则-N表示进程由信号N所终止</p>
</td>
</tr>
<tr>
<td>name</td>
<td>
<p>进程的名称</p>
</td>
</tr>
<tr>
<td>pid</td>
<td>
<p>进程的整数ID</p>
</td>
</tr>
</tbody>
</table>
<p>简单的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
import time
def clock(interval):
    while True:
        print "Current time is %s" % time.ctime()
        time.sleep(interval)
if __name__ == '__main__':
    p = multiprocessing.Process(
        target=clock,
        args=(15,)
    )
    p.start()

#使用扩展子类的方式
class ClockProcess(multiprocessing.Process):
    def __init__(self,interval):
        multiprocessing.Process.__init__(self)
        self.interval = interval
    def run(self):
        while True:
            print "Current time is %s" % time.ctime()
            time.sleep(self.interval)</pre>
<div class="blog_h2"><span class="graybg">进程间通信</span></div>
<p>multiprocessing模块支持<span style="background-color: #c0c0c0;">进程间通信的两种主要形式：管道和队列</span>，这两种方法都是使用消息传递实现的。</p>
<div class="blog_h3"><span class="graybg"><strong>使用队列</strong></span></div>
<p>使用Queue([maxsize])可以创建队列，maxsize是队列中允许的最大项数，省略表示无穷大，具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>cancel_join_thread()</td>
<td>不在进程退出时自动join后台线程。可防止join_thread()方法阻塞</td>
</tr>
<tr>
<td>close() </td>
<td>关闭队列，防止队列中加入更多数据。调用此方法时，后台线程将<span style="background-color: #c0c0c0;">继续写入那些已入队列但尚未写入的数据</span>，但将在此方法完成时马上关闭。如果被垃圾收集，将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如，如果某个使用者正被阻塞在get ()操作 上，关闭生产者中的队列不会导致get ()方法返回错误</td>
</tr>
<tr>
<td>empty()</td>
<td>如果调用此方法时队列为空，返回True。如果其他进程或线程正在往队列中添加项目，结果是不可靠的。也就是说，在返回和使用结果之间，队列中可能已经加入了新的项目</td>
</tr>
<tr>
<td>full()</td>
<td>如果队列已满，则返回True。由于线程的存在，结果也可能不可靠</td>
</tr>
<tr>
<td>get([block [, timeout]]）</td>
<td>返回队列中的一个项。 如果队列为空，此方法将阻塞，直到队列中有项目可用为止。block用于控制阻塞行为，默认为True。 如果设置为False，可能引发Queue.Empty异常。timeout是可选超时时间，在阻塞模式中如果在指定的时间间隔内没有项变为可用，引发Queue.Empty异常</td>
</tr>
<tr>
<td>get_nowait()</td>
<td>等价于get(False)</td>
</tr>
<tr>
<td>join_thread()</td>
<td>当前线程join队列的后台线程。用于在调用close()方法之后，等待所有队列项被消耗</td>
</tr>
<tr>
<td>put(item [, block [, timeout]])</td>
<td>将item放入队列。如果队列已满，此方法将阻塞至有空间可用为止。block控制阻塞行为，默认为True。如果设置为False，将引发Queue.Empty异常。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常</td>
</tr>
<tr>
<td>put_nowait()</td>
<td>等价于put(item, False)</td>
</tr>
<tr>
<td>qsize()</td>
<td>返回队列中目前项目的正确数量。此函数的结果并不可靠</td>
</tr>
</tbody>
</table>
<p>使用JoinableQueue([maxsize])可以创建可连接的共享队列，允许消费者通知生产者消息项已经被成功处理，具有额外的方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>task_done()</td>
<td>消费者使用此方法发出信号，表示通过get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量，将引发ValueError异常</td>
</tr>
<tr>
<td>join()</td>
<td>生产者使用此方法进行阻塞，直到队列中的所有项目均被处理。阻塞将持续到为队列中的每个项目均调用task_done()方法为止。</td>
</tr>
</tbody>
</table>
<p>使用队列通信的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
def consumer(inputq):
    while True:
        item = inputq.get()
        if item == -1: break    #特殊标记时终止
        print item
        inputq.task_done()
def produce(seq, outputq):
    for item in seq:
        outputq.put(item)
if __name__ == '__main__':
    q = multiprocessing.JoinableQueue()
    cons_p = multiprocessing.Process(target=consumer, args=(q,))
    cons_p.daemon = True
    cons_p.start()
    #这里可以继续创建多个消费者进程，但是每个消息只能被一个进程消费
    seq = [0, 1, 2, 3, 4 , -1]  #特殊标记，提示消费者流程结束
    produce(seq, q)
    q.join()  # 当前进程等待队列被消耗完毕</pre>
<div class="blog_h3"><span class="graybg">使用管道</span></div>
<p>可以在进程之间创建一根管道：</p>
<pre class="crayon-plain-tag">#创建管道的函数：duplex表示是否双向通信，默认True
#如果duplex=False，那么conn1只能接收，conn2只能发送
(conn1,conn2) = Pipe([duplex])</pre>
<p>Pipe函数的返回值为Connection对象的元组，Connection对象具有以下属性和方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 250px; text-align: center;"> 方法/属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>close()</td>
<td>关闭连接。如果connection被垃圾收集，将自动调用此方法</td>
</tr>
<tr>
<td>fileno()</td>
<td>返回连接使用的整数文件描述符</td>
</tr>
<tr>
<td>poll([timeout])</td>
<td>如果连接上的数据可用，返回True。timeout指定等待的最长时限。如果省略此参数，方法将立即返回结果。如果将timeout置为None，操作将无限期地等待数据到达</td>
</tr>
<tr>
<td>recv()</td>
<td>接收send()方法返回的对象。如果连接的另一端已经关闭，再也不存在任何数据，将引发EOFError异常</td>
</tr>
<tr>
<td>recv_bytes([maxlength])</td>
<td>接收send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。 如果进入的消息超过了这个最大值，将引发IOError异常，并且在连接上无法进行进一步读取。如果连接的另一端已经关闭，再也不存在任何数据，将引发EOFError异常</td>
</tr>
<tr>
<td>recv_bytes_into(buffer [, offset])</td>
<td>接收一条完整的字节消息，并把它保存在buffer对象中，该对象支持可写入的缓冲区接口（bytearry对象或类似的对象）。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间，将引发BufferTooShort异常</td>
</tr>
<tr>
<td>c.send(obi)</td>
<td>通过连接发送对象。obj是与序列化兼容的任意对象</td>
</tr>
<tr>
<td>send_bytes (buffer [, offset [, size]])</td>
<td>通过连接发送字节数据缓冲区。buffer为支持缓冲区接口的任意对象，offset是缓冲区中的字节偏移量，size是要发送字节数。结果数据以单条消息的形式发出，然后调用recv_bytes()进行接收</td>
</tr>
</tbody>
</table>
<p>如果生产者或消费者中<span style="background-color: #c0c0c0;">都没有使用管道的某个端点，就应将其关闭</span>。如果忘记执行这越步骤，程序可能在消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的， 必须在所有进程中关闭管道后才能生成EOFError异常。因此，<span style="background-color: #c0c0c0;">在生产者中关闭管道不会有任何效果， 除非消费者也关闭了相同的管道端点</span>。</p>
<p>使用管道的生产者——消费者的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
def consumer(pipe):
    outputp, inputp = pipe
    inputp.close()  # 关闭消费者的输入通道
    while True:
        try:
            item = outputp.recv()
        except EOFError:
            break
        print item
    print "Consumer done"
def produce(seq, inputp):
    for item in seq:
        inputp.send(item)

if __name__ == '__main__':
    (outputp, inputp) = multiprocessing.Pipe(True)
    cons_p = multiprocessing.Process(target=consumer, args=((outputp, inputp),))
    cons_p.start()
    outputp.close()  # 关闭生产者的输出通道
    seq = [0, 1, 2, 3, 4]
    produce(seq, inputp)
    # 关闭输入通道
    inputp.close()
    # 等待消费者进程关闭
    cons_p.join()</pre>
<p>双向通信的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
def server(pipe):
    serverp, clientp = pipe
    clientp.close()                #服务器不会使用管道的另一端
    while True:
        try:
            x, y = serverp.recv()  #接收请求消息
        except EOFError:           #如果客户端关闭了管道另一端
            print "Pipe endpoint closed by peer"
            break
        result = x + y
        serverp.send(result)       # 返回处理结果
    print "Server done"
if __name__ == '__main__':
    (serverp, clientp) = multiprocessing.Pipe(True)
    s = multiprocessing.Process(target=server, args=((serverp, clientp),))
    s.start()
    
    #客户端逻辑部分
    serverp.close()                #客户端不会使用管道的另一端
    clientp.send((1, 1))           #发送请求消息
    print clientp.recv()           #打印处理结果
    
    clientp.send(('Hello',' World'))
    print clientp.recv() 
    clientp.close()
    s.join()                       #等待子进程结束</pre>
<div class="blog_h2"><span class="graybg">进程池</span></div>
<p>Pool类可用于创建进程池，可以把各种数据处理任务提交给进程池处理：</p>
<pre class="crayon-plain-tag">#构造函数
Pool( [numprocess [, initializer [, initargs]]])
# numprocess   池中的进程数，省略则使用cpu_count()的值
# initializer  进程启动时执行的可调用对象，默认None
# initargs     传递给initializer的参数</pre>
<p>Pool的实例支持以下方法： </p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 300px; text-align: center;"> 方法</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 250px;">apply(func [, args [, kwargs]])</td>
<td>在池中一个工作进程中执行func(*args, **kwargs)并返回结果</td>
</tr>
<tr>
<td>apply_async(func [, args [, kwargs, [cb]]])</td>
<td>在池中一个工作进程中异步执行func(*args, **kwargs)并返回一个AsyncResult对象，该对象可用于稍后获取结果。cb为一可调用对象，当异步执行的结果可用时，会回调之</td>
</tr>
<tr>
<td>close()</td>
<td>关闭进程池，防止进行进一步操作</td>
</tr>
<tr>
<td>terminate()</td>
<td>立即终止所有工作进程，同时不执行任何清理或结束任何挂起工作，如果进程池北垃圾回收，该函数自动调用</td>
</tr>
<tr>
<td>join()</td>
<td>等待所有工作进程退出。此方法只能在close()或terminate()方法之后调用</td>
</tr>
<tr>
<td>map(func, iterable [, chunksize])</td>
<td>将可调用对象func应用给iterable中的所有项目，然后以列表的形式返回结果。通过将iterable划分为多块并将工作分派给工作进程，可以并行地执行这项操作。chunksize为每块中的项目数</td>
</tr>
<tr>
<td>map_async(func, iterable [, chunkslze [, cb]])</td>
<td>同map()函数，但结果的返回是异步的。返回值是AsyncResult类的实例</td>
</tr>
<tr>
<td>imap(func, iterable [, chunksize]) </td>
<td>map()函数的版本之一，返回迭代器而非结果列表</td>
</tr>
<tr>
<td>imap_unordered (iterable [, chunksize ])</td>
<td>同imap()函数，但从工作进程接收结果时，返回结果的次序是任意的</td>
</tr>
</tbody>
</table>
<p>AsyncResult的实例支持以下方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">方法 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>get([timeout])</td>
<td>返回结果，如果有必要则等待结果到达。timeout是可选的超时。如果结果在指定时间内没有到达，将引发TimeoutError异常。如果远程操作中引发了异常，它将在调用此方法时再次被引发</td>
</tr>
<tr>
<td>ready()</td>
<td>如果调用完成，返回True</td>
</tr>
<tr>
<td>successful()</td>
<td>如果调用完成且没有引发异常，返回True。如果在结果就绪之前调用此方法，将引发AssertionError异常</td>
</tr>
<tr>
<td>wait ([timeout])</td>
<td>等待结果变为可用。timeout是可选的超时</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">数据共享与同步</span></div>
<p>通常，进程之间彼此是完全孤立的，唯一的通信方式是队列或管道。可以使用两个对象来表示共享数据。这些对象使用了<span style="background-color: #c0c0c0;">共享内存（通过mmap模块）使访问多个进程成为可能</span>。可以创建以下类型的共享数据：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 共享数据类型</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 250px;">Value(typecode, arg1, ... argN, lock)</td>
<td>
<p>在共享内存中创建ctypes对象，typecode可以是类似array模块的类型代码，或者ctypes模块的类型对象（例如ctypes.c_int）。参数arg1...argN传递给类型的构造函数，lock必须使用关键字参数调用，如果为True（默认）则会创建一个新的锁定来包含对值的访问，如果传入现有锁对象（Lock或者RLock），则该锁用于同步访问。</p>
<p>可以使用value属性访问底层的数值</p>
</td>
</tr>
<tr>
<td>RawValue (typecode, arg1, ... argN)</td>
<td> 同Value对象，但不存在锁定。</td>
</tr>
<tr>
<td>Array(typecode, initializer, lock)</td>
<td>在共享内存中创建ctypes数组。typecode描述了数组的内容，意义与Value()函数中的相同。 initializer要么是设罝数组初始大小的整数，要么是项目序列，其值和大小用于初始化数组。lock 是只能使用关键字调用的参数，意义与Value()函数中相同。如果a是Array创建的共享数组的实例，可使用标准的Python索引、切片和迭代操作访问它的内容，其中每种操作均由锁定进行同步。对于字节字符串，a还具有value属性，可以把整个数组当作一个字符串进行访问</td>
</tr>
<tr>
<td>RawArray (typecode, initializer)</td>
<td>同Array，但不存在锁定</td>
</tr>
</tbody>
</table>
<p>可以使用以下功能与threading模块类似的同步原语：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 原语</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Lock </td>
<td>互斥锁</td>
</tr>
<tr>
<td>RLock </td>
<td>可重入的互斥锁（同一进程可以多次获得它，同时不会造成阻塞)</td>
</tr>
<tr>
<td>Semaphore</td>
<td>信号量</td>
</tr>
<tr>
<td>BoundedSemaphore</td>
<td>有边界的信号量</td>
</tr>
<tr>
<td>Event </td>
<td>事件</td>
</tr>
<tr>
<td>Condition</td>
<td>条件变量</td>
</tr>
</tbody>
</table>
<p>结合信号量来把数组发送给另外一个进程的例子：</p>
<pre class="crayon-plain-tag">import multiprocessing
class FloatChannel(object):
    def __init__(self, maxsize):
        self.buffer = multiprocessing.RawArray('d', maxsize)
        self.buffer_len = multiprocessing.Value('i')
        self.empty = multiprocessing.Semaphore(1)
        self.full = multiprocessing.Semaphore(0)
    def send(self, values):
        # 等待缓冲区为空的信号
        self.empty.acquire()
        n = len(values)
        self.buffer_len = n
        self.buffer[:n] = values
        # 设置缓冲区已满的标记
        self.full.release()
    def recv(self):
        self.full.acquire()
        # 复制缓冲区中的值，注意可以使用切片语法
        values = self.buffer[:self.buffer_len.value]
        # 设置缓冲区已空的标记
        self.empty.release()
        return values
    
def consumer(count , fc):
    for i in xrange(count):
        fc.recv()
        
if __name__ == '__main__':
    fc = FloatChannel(100000)
    p = multiprocessing.Process(target=consumer, args=(1000, fc))
    p.start()
    
    values = [float(x) for x in xrange(100000)]
    for i in xrange(1000):
        fc.send(values)
    print "Produce done"
    p.join()</pre>
<div class="blog_h2"><span class="graybg">托管对象（共享任意对象）</span></div>
<p>上一节的数据同步，只能使用共享值、数组等基本类型，如果需要使用Python对象则无能为力。Python提供了“管理器”这种独立的子进程，可以用于管理多个进程之间的共享对象。“管理器”作为服务器进程来运行，其他进程则通过代理访问共享对象，相当于客户端。</p>
<p>使用Manager()函数可以创建一个管理器，其返回位于multiprocessing.managers模块中的SyncManager对象</p>
<div class="blog_h2"><span class="graybg">连接网络上的进程</span></div>
<p>使用multiprocessing模块的程序可以与运行在同一台计算机上的其他进程或者位于远程系统 上的进程进行消息传递。可以使用Client来连接到其它进程：</p>
<pre class="crayon-plain-tag">#位于multiprocessing.connections子模块
connections.Client (address [, family [, authenticate [, authkey]]])
#address       代表网络地址的元组(hostname, port)
#family        表示地址格式的字符串，省略则从address自动推断
#authenticate  是否使用摘要身份验证
#authkey       包含身份验证密钥的字符串，省略则使用current_process().authkey</pre>
<p>对方进程必须作为服务器，处于监听状态：</p>
<pre class="crayon-plain-tag">connections.Listener([address, [,family [, backlog[, authenticate [,authkey]]]]])</pre>
<p>Listener的实例具有以下方法或属性： </p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 方法/属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 150px;">accept()</td>
<td>接受一个新连接，并返回一个Connection对象。如果身份验证失败，将引发AuthenticationError</td>
</tr>
<tr>
<td>address</td>
<td>侦听器正在使用的地址</td>
</tr>
<tr>
<td>close()</td>
<td>关闭侦听器正在使用的管道或套接字</td>
</tr>
<tr>
<td>last_accepted </td>
<td>接受的最后一个客户端的地址</td>
</tr>
</tbody>
</table>
<p>下面是一个简单的例子：</p>
<pre class="crayon-plain-tag">def server():
    from multiprocessing.connection import Listener
    serv = Listener(('127.0.0.1', 15000), authkey="12345")
    while True:
        conn = serv.accept()
        while True:
            try :
                x, y = conn.recv()
            except EOFError:  # 如果客户端断开连接
                print "Connection closed by peer"
                break
            result = x + y
            conn.send(result)
        conn.close()

if __name__ == '__main__':
    import multiprocessing
    s = multiprocessing.Process(target=server)
    s.start()
    from multiprocessing.connection import Client
    conn = Client(('127.0.0.1', 15000), authkey="12345")
    conn.send((1, 1))
    print conn.recv()
    conn.close()</pre>
<div class="blog_h2"><span class="graybg">常用工具函数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">函数 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>active_children()</td>
<td>列出所有活动子进程的Process对象</td>
</tr>
<tr>
<td>cpu_count()</td>
<td>返回系统上的CPU数量，如果能够确定的话</td>
</tr>
<tr>
<td>current_prpcess()</td>
<td>返回当前进程的Process对象</td>
</tr>
<tr>
<td>freeze_support()</td>
<td>在使用各种打包工具（如py2exe)进行冻结的应用程序中，此函数应该作为主程序的首行。使用此函数可以防止与在冻结的应用程序中启动子进程相关的运行时错误</td>
</tr>
<tr>
<td>get_logger()</td>
<td>返回与多进程处理模块相关的日志记录对象，如果它不存在则创建之。返回的记录器不会把消息传播给根记录器，级别logging.NOTSET，会将所有日志消息打印到标准错误上</td>
</tr>
<tr>
<td>set_executable(executable)</td>
<td>设置用于执行子进程的Python可执行程序的名称。这个函数只定义在Windows上</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">threading模块</span></div>
<p>该模块提供Thread类和各种同步原语，用于编写多线程的程序。</p>
<div class="blog_h2"><span class="graybg">Thread类</span></div>
<p>Thread类用于表本单独的控制线程。使用下面的函数可以创建一个新线程:</p>
<pre class="crayon-plain-tag">Thread (group=None, target=None, name=None, args=(), kwarga={})
# group 预留字段
# target 可调用对象，线程启动时，其run方法将调用该对象
# name 线程的名称
# args、kwargs 传递给target的参数</pre>
<p>线程支持以下实例方法和属性：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 方法/属性</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>start()</td>
<td>通过在一个单独的控制线程中调用run ()方法，启动线程。此方法只能调用一次</td>
</tr>
<tr>
<td>run()</td>
<td>线程启动时将调用此方法。默认情况下，它将调用传递到构造函数中的目标函数。还可以在Thread 的子类中重新定义此方法</td>
</tr>
<tr>
<td>join([timeout])</td>
<td>等待直到线程终止或者出现超时为止。timeout是一个浮点数，用于指定以秒为单位的超时时间。 线程不能join自身，而且在线程启动之前就连接它将出现错误</td>
</tr>
<tr>
<td>is_alive()</td>
<td>如果线程是活动的，返回True，否则返回False。从start()方法返回的那一刻开始，线程就是活动的，直到它的run()方法终止为止</td>
</tr>
<tr>
<td>name</td>
<td>线程名称</td>
</tr>
<tr>
<td>ident</td>
<td>整数线程标识符。如果线程尚未启动，它的值为None</td>
</tr>
<tr>
<td>daemon</td>
<td>线程的布尔型后台标志。必须在调用start ()方法之前设置这个标志，它的初始值从创建线程的后台状态继承而来。当不存在任何活动的非后台线程时，整个Python程序将退出。所有程序<span style="background-color: #c0c0c0;">都有一个主线程，代表初始的控制线程，它不是后台线程</span></td>
</tr>
</tbody>
</table>
<p>下面是一个使用线程的例子：</p>
<pre class="crayon-plain-tag">import threading
import time
class ClockThread(threading.Thread):
    #除了__init__、run以外，改写别的方法
    def __init__(self, interval):
        threading.Thread.__init__(self)   #子类如果扩展__init__，必须调用Thread的构造函数
        self.daemon = True
        self.interval = interval
    def run(self):
        while True:
            print "Current time is %s" % time.ctime()
            time.sleep(self.interval)
if __name__ == '__main__':
    t = ClockThread(15)
    t.start()   
    time.sleep(150)</pre>
<div class="blog_h2"><span class="graybg">定时器Timer</span></div>
<p>用于延迟执行某一函数：</p>
<pre class="crayon-plain-tag">Timer(interval, func [, args [, kwargs]])
#interval 延迟执行的秒数
#func 需要执行的函数
#args、kwargs 函数参数</pre>
<p>Timer具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="width: 150px;">start()</td>
<td>启动定时器</td>
</tr>
<tr>
<td>cancel() </td>
<td>如果函数尚未执行，可用于取消定时器 </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">锁（Lock）</span></div>
<p>锁定是一个同步原语，状态是“已锁定”或“未锁定”之一。两个方法acquire() 和release()用于修改锁定的状态。如果状态为已锁定，尝试获取锁定将被阻塞，直到锁定被释放为 止。如果有多个线程等待获取锁定，当锁定被释放时，只有一个线程能获得它。等待线程获得锁定的顺序没有定义</p>
<p>使用构造函数Lock()可以创建锁的实例，<span style="background-color: #c0c0c0;">初始状态为未锁定</span>。具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">方法 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>acquire([blocking])</td>
<td>获取锁定，如果有必要，需要阻塞到锁定释放为止。如果提供参数blocking并将它设为False，当无法获取锁定时将立即返回False，否则立即返回True</td>
</tr>
<tr>
<td>release ()</td>
<td>释放一个锁定。当锁定处于未锁定状态时，或者从<span style="background-color: #c0c0c0;">与原本调用acquire()方法的线程不同的线程</span>调用此方法，将出现错误</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">使用锁时的注意点</span></div>
<p>使用诸如Lock、RLock或Semphore之类的锁定原语时，必须多加小心。锁定的错误管理经常导致死锁或者竞态条件。可以使用下面代码中提到的两种方式保证锁的正确释放：</p>
<pre class="crayon-plain-tag">import threading
lock = threading.Lock()
#方法一：使用try-finally语句
try:
    lock.acquire()
    #执行同步操作
finally:
    lock.release()
#方法二：使用上下文管理协议
#进入时自动获取锁定，离开时自动释放锁定
with lock:
    #执行同步操作
    pass</pre>
<p>此外，应当尽量避免同时获取多个锁定，容易导致死锁。</p>
<div class="blog_h2"><span class="graybg">可重入锁RLock</span></div>
<p>可重入锁定是一个类似于Lock同步原语，但同一个线程可以多次获取它。这允许拥有锁定的线程执行嵌套的acquire()和release()操作。在这种情况下，只有最外面的release()操作才能将锁定重置为未锁定状态。进行若干次lock()后，必须在进行对应次数的release()才能彻底释放锁。</p>
<div class="blog_h2"><span class="graybg">信号量和有界信号量</span></div>
<p>信号量是一个基于计数器的同步原语，<span style="background-color: #c0c0c0;">每次调用acquire()方法时此计数器减1，每次调用 release()方法时此计数器加1</span>。如果计数器为0, acquire ()方法将会阻塞，直到其他线程调用release ()方法为止。</p>
<p>可以使用Semaphore([value])来创建信号量，其中value为计数器的初始值，默认为1。信号量包含以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">方法 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>acquire([blocking])</td>
<td>获取信号量。如果进入时内部计数器大于0，此方法将把它的值减1，然后立即返回。如果它的值为0，此方法将阻塞，直到另一个线程调用release()方法。blocking参数的行为与Lock类似</td>
</tr>
<tr>
<td>release ()</td>
<td>通过将内部计数器的值加1来释放一个信号量。如果计数器为0，而且另一个线程正在等待，该线程将被唤醒。如果有多个线程正在等待，只能从它的acquire()调用返回其中一个。线程释放的顺序并不确定。</td>
</tr>
</tbody>
</table>
<p>BoundedSemaphore的行为与Semaphore相同，但是限制但release()操作的次数不能超过acquire()的次数。</p>
<div class="blog_h2"><span class="graybg">事件</span></div>
<p>事件用于在线程之间通信。一个线程发出“事件”信号，一个或多个其他线程等待它，具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">方法 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>is_set()</td>
<td>只有当内部标志为True时才返回True</td>
</tr>
<tr>
<td>set ()</td>
<td>将内部标志置为True。等待它变为True的所有线程都将被唤醒</td>
</tr>
<tr>
<td>clear()</td>
<td>将内部知志重置为False</td>
</tr>
<tr>
<td>wait( [timeout] )</td>
<td>阻塞直到内部标志为True。如果进入时内部标志为True，此方法将立即返回。否则，它将阻塞直到另个线程调用set()方法将志置为True，或者直到出现可选的超时。timeout是个浮点数，用于指定以秒为单位的超时期限</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">条件变量</span></div>
<p>条件变量是构建在另一个锁定上的同步原语，当需要线程关注特定的<span style="background-color: #c0c0c0;">状态变化或事件的发生</span>时将使用这个锁定。典型的用法是生产者-消费者场景，其中一个线程生产的数据供另一个线程使用。使用构造函数Condition([lock])可以创建条件变量，其中lock是Lock或者RLock的实例，如果不提供则创建新的锁供食用。Condition具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>acquire (*args)</td>
<td>
<p>获取底层锁定。此方法将调用底层锁定上对应的acquire方法</p>
</td>
</tr>
<tr>
<td>release ()</td>
<td>释放底层锁定。此方法将调用底层锁定上对应的release()方法</td>
</tr>
<tr>
<td>wait( [timeout])</td>
<td>等待直到获得通知或出现超时为止。此方法在调用线程<span style="background-color: #c0c0c0;">已经获取锁定之后调用。调用时，将释放底层锁定，而且线程将进入睡眠状态，直到另一个线程在条件变量上执行notify()或notifyAll() 方法将其唤醒为止</span>。在线程被唤醒之后，线程将重新获取锁定，方法也会返回，timeout是浮点数， 单位为秒。如果这段时限耗尽，线程将被唤醒，锁定将被重新获取</td>
</tr>
<tr>
<td>notify([n])</td>
<td>唤醒一个或多个等待此条件变量的线程。此方法只会在调用线程已经获取锁定之后调用，而且如果没有正在等待的线程，它就什么也不做。n指定要唤醒的线程数量，默认为1。被唤醒的线程在它们 新获取锁定之前不会从wait ()调用返回</td>
</tr>
<tr>
<td>notify_all()</td>
<td>唤醒所有等待此条件的线程</td>
</tr>
</tbody>
</table>
<p>条件变量的简单例子：</p>
<pre class="crayon-plain-tag">import threading
c = threading.Condition()
def producer():
    while True:
        c.acquire()
        produce_item()
        c.notify()
        c.release()
#使用条件变量时需要注意的是，如果存在多个线程等待同一个条件，notify()操作可能唤醒它们
#中的一个或多个（这种行为通常取决于底层的操作系统)。因此，始终有这样的可能：某个线程被
#唤醒后，其等待的条件已经不满足，因此可能需要循环继续等待下一个信号
def consumer():
    while True:
        c.acquire()
        while not item_is_available():
            #释放锁定，生产者可以获取并生产物品
            c.wait()
        c.release()
        consume_item()</pre>
<div class="blog_h2"><span class="graybg">线程的终止与挂起</span></div>
<p>线程没有任何方法可用于强制终止或挂起。这是设计上的原因，因为如果某个线程已经获取了锁定，在它能够释放锁定之前强制终止或挂起它，将导致整个应用程序出现死锁。此外，终止时一般不能简单地“释放锁定”，因为复杂的线程同步经常涉及锁定和解除锁定操作，而这些操作在执行时的次序要十分精确。 如果要为终止或挂起提供支持，需要自己构建这些功能。一般的做法是在循环中运行线程，这个循环的作用是定期检査线程状态以决定它是否应该终止</p>
<div class="blog_h2"><span class="graybg">常用工具函数</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 函数</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>active_count()</td>
<td>
<p>返回当前活动的Thread对象数量</p>
</td>
</tr>
<tr>
<td>current_thread() </td>
<td>返回对应于调用者的控制线程的Thread对象</td>
</tr>
<tr>
<td>enumerate()</td>
<td>列出当前所有活动的Thread对象</td>
</tr>
<tr>
<td>local()</td>
<td>返回local对象，用于保存线程本地的数据，应该保证此对象在每个线程中是唯一的</td>
</tr>
<tr>
<td>setprofile(func)</td>
<td>设置一个配置文件函数，用于已创建的所有线程。func在每个线程开始运行之前被传递给 sys.setprofile()函数</td>
</tr>
<tr>
<td>settrace(func)</td>
<td>设置一个跟踪函数，用于已创建的所有线程。func在每个线程开始运行之前被传递给sys.settrace()函数</td>
</tr>
<tr>
<td>stack_size([size]）</td>
<td>返回创建新线程时使用的栈大小。size 的值可以是32768 (32KB)或更大，而且是4096 (4KB)的倍数，这样可移植性更好</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">全局解释器锁定</span></div>
<p>Python解释器被一个锁定保护，<span style="background-color: #c0c0c0;">该锁定只允许一次执行一个线程</span>，即便存在多个可用的处理器。 在计算密集型程序中，这严重限制了线程的作用。事实上，<span style="background-color: #c0c0c0;">在计算密集型程序中使用线程，经常比仅仅按照顺序执行同样的工作慢得多</span>。<span style="background-color: #c0c0c0;">因此，实际上应该只在主要关注I/O的程序，如网络服务器中使用线程</span>。对于计算密集程度更高的任务，最好使用C扩展模块或multiprocessing模块来代替。C扩展具有释放解释器锁定和并行运行的选项，可以做到当释放锁定时不与解释器进行交互。 multiprocessing模块将工作分派给不受锁定限制的单独子进程</p>
<div class="blog_h1"><span class="graybg">queue模块</span></div>
<p>queue模块（在Python 2中叫Queue)实现了各种多生产者——多消费者队列，可用于在执行的多个线程之间安全地交换信息。 该模块定义了3种不同的队列类：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 队列类型</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Queue([maxsize])</td>
<td>创建一个FIFO队列。maxsize是队列中可以放入的项目的最大数量。如果省略maxsize参数或将它置为0，队列大小无限制</td>
</tr>
<tr>
<td>ListQueue([maxsize])</td>
<td>创建一个LIFO队列（栈)</td>
</tr>
<tr>
<td>PriorityQueue([maxsize])</td>
<td>创建一个优先级队列，其中项目按照优先级从低到髙依次排好。使用这种队列时，项目应该是 (priority, data)形式的元组，其中priority是一个数字</td>
</tr>
</tbody>
</table>
<p>队列具有以下实例方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>qsize()</td>
<td>
<p>返回队列的正确大小。因为其他线程可能正在更新队列，此方法返回的数字不完全可靠</p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>empty()</td>
<td>如果队列为空，返回True,否则返回False</td>
</tr>
<tr>
<td>full()</td>
<td>如果队列已满，返回True,否则返回False</td>
</tr>
<tr>
<td>put (item [,block [, timeout]])</td>
<td>将item放入队列。如果可选参数block为True (默认值)，调用者将被阻塞直到队列中出现可用的空闲位置为止。否则队列满时将引发Full异常。timeout提供可选的超时值， 单位为秒。如果出现超时，将引发Full异常</td>
</tr>
<tr>
<td>put_nowait (item)</td>
<td>等价于g.put (item, False)</td>
</tr>
<tr>
<td>get ([block [, timeout]])</td>
<td>从队列中删除一项，然后返回这个项目。如果可选参数block为True (默认值)，调用者将阻塞， 直到队列中出现可用的空闲位置。否则队列为空时将引发Empty异常，timeout提供可选的超时值，单位为秒。如果出现超时，将引发Empty异常。 </td>
</tr>
<tr>
<td>get_nowait ()</td>
<td>等价于get (0)方法</td>
</tr>
<tr>
<td>task_done()</td>
<td>队列中数据的消费者用来指示对于项目的处理已经结束。如果使用此方法，那么从队列中删除的每一项都应该调用一次</td>
</tr>
<tr>
<td>join()</td>
<td>阻塞直到队列中的所有项目均被删除和处理为止。一旦为队列中的每一项都调用了一次 task_done ()方法，此方法将会直接返回</td>
</tr>
</tbody>
</table>
<p>使用队列一般可以简化多线程的程序。例如，可以使用共享队列将线程连接在一起，而不必依赖于必须由锁定保护的共享状态。在这种模型中，工作者线程一般充当数据的消费者。下面是一个简单的例子：</p>
<pre class="crayon-plain-tag">import threading 
from queue import Queue
class WorkerThread(threading.Thread):	
   def __init__(self,*args,**kwargs)： 
       threading.Thread.__init__(self, *args, **kwargs) 
       self.input_queue = Queue()
   def send(self,item):
       self.input_queue.put(item)
   def close(self):
       self.input_queue.put(None) 
       self.input_queue.join()
   def run(self):
       while True:
           item = self.input_queue.get()
           if item is None:
               break
           print item #处理条目
           self.input_queue.task_done()
       self.input_queue.task_done()
       return
#使用示例
w = WorkerThread()
w.start()
w.send('Hello')   #把条目发送给工作线程处理
w.close()</pre>
<p>上面的例子几乎完全等同于协程。如果要执行的工作不涉及任何阻塞操作，可以将run()方法重新实现为协程，这就省却了使用线程的麻烦。后一种方法的运行速度可能更快，因为节省了线程上下文切换带来的开销</p>
<div class="blog_h1"><span class="graybg">协程与微线程</span></div>
<p>在某些类型的应用程序中，可以<span style="background-color: #c0c0c0;">使用一个任务调度器和一些生成器或协程实现协作式用户空间多线程，这有时称为微线程</span>。这种技术的一种常见用法是在<span style="background-color: #c0c0c0;">需要管理大量的已打开文件或套接字的程序中</span>，例如一台需要同时管理 1000个客户端连接的网络服务器。此时的解决方案是<span style="background-color: #c0c0c0;">联合使用异步I/O或轮询（使用select模块）与处理I/O事件的任务调度器</span>，而不是创建1000个线程，因为过多的线程会导致大量系统资源浪费在上下文切换上。 这种编程技术的基础概念是这样产生的：生成器或协程函数中的yield语句挂起函数的执行，直到稍后使用next () 或者send()操作进行恢复为止。这样就可以使用一个调度器循环在一组生成器函数之间协作多个任务。</p>
<p>基于协程的并发编程的例子：<a href="/python-network-programming#echo-service-by-coroutine">基于协程技术实现的异步Echo服务</a></p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/python-concurrent-programming">Python并发编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/python-concurrent-programming/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Linux信号、进程和会话</title>
		<link>https://blog.gmem.cc/linux-signals-processes-and-sessions</link>
		<comments>https://blog.gmem.cc/linux-signals-processes-and-sessions#comments</comments>
		<pubDate>Mon, 10 Aug 2009 06:47:11 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Linux知识]]></category>
		<category><![CDATA[Linux编程]]></category>
		<category><![CDATA[并发编程]]></category>
		<category><![CDATA[系统编程]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=5772</guid>
		<description><![CDATA[<p>进程 进程和信号是Linux操作环境的基础部分，控制着Linux和其它类UNIX系统的几乎所有活动。 UNIX标准对进程的定义：其中运行着一个或者多个线程的地址空间，以及这些线程需要的系统资源。Linux系统的进程是非常轻量级的。 进程基础知识 每个Linux进程包含以下部分： PID：进程的唯一表示，是范围从2~32768的正整数，数字回绕一圈后，重新从2开始计数（数字1被系统第一个进程init占用） 程序代码：以自动方式加载到内存，正常情况下Linux进程不能对用来存放程序代码的内存区域进行写操作，因而可以被多个进程安全的共享 函数库：系统函数库也可以被共享，不管多少进程调用printf，内存中只需要它的一份副本即可，这种做法类似于微软的DLL机制但更为复杂 进程数据：存放进程的全局变量 栈空间：进程有属于自己的栈空间，用于保存函数中的局部变量、控制函数的调用和返回 环境空间：包含专为该进程建立的环境变量 程序计数器：记录进程执行到的位置，即在执行线程中的位置。进程可以包含多个执行线程 Linux系统使用一个被称为进程表的结构来存放当前加载到内存的所有进程的信息。这些信息包括：进程ID、进程状态、进程命令字符串以及其它一些ps命令输出的信息。操作系统通过PID对进程进行管理，早起的UNIX系统只能同时运行256个进程。 进程状态 注意：这些状态也适用于线程。进程中的线程通常处于不同的状态。 通过ps的STAT列，可以查看进程的状态，其代码如下表：  STAT代码 说明  S 睡眠。通常是在等待某个事件的发生，如信号、输入、Time slot R <a class="read-more" href="https://blog.gmem.cc/linux-signals-processes-and-sessions">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/linux-signals-processes-and-sessions">Linux信号、进程和会话</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">进程</span></div>
<p>进程和信号是Linux操作环境的基础部分，控制着Linux和其它类UNIX系统的几乎所有活动。</p>
<p>UNIX标准对进程的定义：其中运行着一个或者多个线程的<span style="background-color: #c0c0c0;">地址空间</span>，以及这些线程需要的<span style="background-color: #c0c0c0;">系统资源</span>。Linux系统的进程是<span style="background-color: #c0c0c0;">非常轻量级</span>的。</p>
<div class="blog_h2"><span class="graybg">进程基础知识</span></div>
<p>每个Linux进程包含以下部分：</p>
<ol>
<li>PID：进程的唯一表示，是范围从2~32768的正整数，数字回绕一圈后，重新从2开始计数（数字1被系统第一个进程init占用）</li>
<li>程序代码：以自动方式加载到内存，正常情况下Linux进程不能对用来存放程序代码的内存区域进行写操作，因而可以被多个进程安全的共享</li>
<li>函数库：系统函数库也可以被共享，不管多少进程调用printf，内存中只需要它的一份副本即可，这种做法类似于微软的DLL机制但更为复杂</li>
<li>进程数据：存放进程的全局变量</li>
<li>栈空间：进程有属于自己的栈空间，用于保存函数中的局部变量、控制函数的调用和返回</li>
<li>环境空间：包含专为该进程建立的环境变量</li>
<li>程序计数器：记录进程执行到的位置，即在执行线程中的位置。进程可以包含多个执行线程</li>
</ol>
<p>Linux系统使用一个被称为<span style="background-color: #c0c0c0;">进程表</span>的结构来存放当前加载到内存的<span style="background-color: #c0c0c0;">所有进程</span>的信息。这些信息包括：进程ID、进程状态、进程命令字符串以及其它一些ps命令输出的信息。操作系统通过PID对进程进行管理，早起的UNIX系统只能同时运行256个进程。</p>
<div class="blog_h3"><span class="graybg">进程状态</span></div>
<p>注意：<span style="background-color: #c0c0c0;">这些状态也适用于线程</span>。进程中的线程通常处于不同的状态。</p>
<p>通过ps的STAT列，可以查看进程的状态，其代码如下表：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> STAT代码</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>S</td>
<td>睡眠。通常是在等待某个事件的发生，如信号、输入、Time slot</td>
</tr>
<tr>
<td>R</td>
<td>运行。严格来说应是“可运行”，即在运行队列中，处于正在执行或即将运行状态</td>
</tr>
<tr>
<td>D</td>
<td>
<p>不可中断的睡眠（Uninterruptible sleep）。通常是在等待输入或输出（网络、磁盘、其它外设的IO）完成</p>
<p>处于此状态的进程，无法处理信号（<span style="background-color: #c0c0c0;">无法被信号唤醒，只能被它所等待的东西唤醒，或超时，如果在睡眠前设置了超时的话</span>），即使是kill -9 也无法处理</p>
<p>不允许中断的原因是，保护系统数据一致，防止数据读取错误</p>
</td>
</tr>
<tr>
<td>T</td>
<td>
<p>停止。通常被Shell作业控制所停止，或者进程正处于调试器的控制下</p>
<p>在Terminal中键入Ctrl + Z会导致当前<span style="background-color: #c0c0c0;">前台进程停止，暂停运行</span>。<span style="background-color: #c0c0c0;">此时输入bg，则让该进程继续在后台运行</span></p>
</td>
</tr>
<tr>
<td>Z</td>
<td>
<p>死（Defunct）进程或者僵尸（Zombie）进程</p>
<p>子进程死亡后，处于僵尸状态，其父进程负责收集其退出码等信息并完全释放它</p>
</td>
</tr>
<tr>
<td>N</td>
<td>低优先级任务（nice）</td>
</tr>
<tr>
<td>W</td>
<td>分页。不适用于2.6+内核</td>
</tr>
<tr>
<td>s</td>
<td>进程是Session leader</td>
</tr>
<tr>
<td>+</td>
<td>进程属于前台进程组</td>
</tr>
<tr>
<td>l</td>
<td>进程是多线程的</td>
</tr>
<tr>
<td>&lt;</td>
<td>高优先级任务</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">系统进程</span></div>
<p>一般而言，每个Linux进程都是由另外一个被称为<span style="background-color: #c0c0c0;">父进程</span>的进程所启动的，前者相对的被称为<span style="background-color: #c0c0c0;">子进程</span>。在Linux系统启动时，它运行一个PID为1的init进程，可以把该进程看做操作系统的进程管理器，它是所有进程的祖先进程。</p>
<p>启动进程并等待其结束，是Linux中最基本的进程管理任务。应用程序可以通过fork、exec、wait等系统调用完成这些任务。</p>
<div class="blog_h3"><span class="graybg">进程调度</span></div>
<p>每个进程被分配以非常短暂的时间片，在时间片范围内进程代码被CPU执行，由于CPU非常快、时间片又非常短，你会感觉到多个程序同时运行的假象。Linux内核使用进程调度器来决定下一个时间片应该分配给哪个进程，其判断的依据是进程的优先级，优先级高的进程获得的时间片更多。在Linux中进程的运行时间<span style="background-color: #c0c0c0;">不可能超过分配给它的时间片</span>，Linux使用的是<span style="background-color: #c0c0c0;">抢占式处理</span>，因此进程的挂起、继续运行不需要彼此之间的协作。</p>
<div class="blog_h3"><span class="graybg">进程死亡</span></div>
<p>进程调用<pre class="crayon-plain-tag">exit()</pre> 系统调用可以让自身退出，进程占用的内存将被释放，并利用信号通知其父进程。</p>
<p>父进程可能先于子进程死亡，这种情况下，init进程可以成为子进程的养父。</p>
<p>子进程在死亡后，将处于僵尸状态。这种状态的进程不能被调度，并占据少量的系统资源，以保证其父进程可以访问其退出码等信息。父进程负责完全的释放子进程。</p>
<div class="blog_h3"><span class="graybg">进程和线程</span></div>
<p>进程包含：虚拟地址空间、打开的系统对象的描述符、安全上下文、进程标识符、环境变量、最小-最大工作集大小，以及最少一个线程 —— 主线程。</p>
<p>线程是进程内部的一个可调度的实体（执行路径），进程内的<span style="background-color: #c0c0c0;">所有线程共享地址空间、打开的描述符</span>。每个线程维护自己的<span style="color: #242729;">exception handlers、调度优先级、线程本地存储、线程标识符、线程上下文结构。线程可以具有自己的安全上下文。</span></p>
<p><span style="color: #242729;">线程上下文中包括的数据项：寄存器数据、内核栈、线程环境块、位于进程地址空间的用户栈。</span></p>
<div class="blog_h2"><span class="graybg">Linux进程相关API</span></div>
<div class="blog_h3"><span class="graybg">启动进程</span></div>
<p>可以在程序内部启动另外一个程序，从而创建新的进程，可以通过库函数system()完成：</p>
<pre class="crayon-plain-tag">#include &lt;stdlib.h&gt;
/**
 * 运行指定的命令并等待其完成
 * @param cmd 需要执行的命令
 * @return 如果无法启动Shell返回127，其它错误返回-1，否则返回所执行命令的退出码
 */
int system( const char *cmd );</pre>
<p>上述函数的重大缺点是必须等待子进程的完成，并且依赖于Shell，因此使用的不多。</p>
<div class="blog_h3"><span class="graybg">替换进程映像</span></div>
<p>exec系列函数用于<span style="background-color: #c0c0c0;">把当前进程替换为一个新的进程（新进程执行结束后不会返回原进程）</span>。 新的程序启动后，原有的程序就不再运行了：</p>
<pre class="crayon-plain-tag">#include &lt;unistd.h&gt;
char **environ; // 该全局变量可用来设置传递到新程序的环境变量

// ***p函数通过搜索PATH环境变量来查找新的可执行文件的路径，如果目标程序不在PATH中，则path参数应当使用绝对路径
// ***e函数支持通过envp数组指定环境变量

// 下面三个函数支持变长参数列表，此列表以一个空指针结束
int execl(const char *path, const char *arg0, ..., (char *)0);
int execlp(const char *file, const char *arg0, ..., (char *)0);
int execle(const char *path, const char *arg0, ..., (char *)0, char *const envp[]);

// 下面三个函数使用数组来表示参数列表
int execv( const char *path, char * const argv[] );
int execvp( const char *file, char * const argv[] );
int execve( const char *path, char * const argv[], char * const envp[] );

//举例：
char * const ps_argv[] = { "ps", "ax", 0 };
char * const ps_envp[] = { "PATH=/bin:/usr/bin", "TERM=console", 0 };
execl("/bin/ps", "ps", "ax", 0);
execlp("ps", "ps", "ax", 0);
execle("/bin/ps", "ps", "ax", 0, ps_envp);
execv("/bin/ps", ps_argv);
execvp("ps", ps_argv);
execve("/bin/ps", ps_argv, ps_envp);</pre>
<p>一般情况下，<span style="background-color: #c0c0c0;">exec函数是不会返回的，除非发生错误</span>，此时返回-1并设置errno。</p>
<p>exec启动的新进程保留了原进程的许多特性，特别是，原进程打开的文件描述符仍然有效，除非这些描述符的close on exec flag标记位被设置。任何在原进程中打开的目录流都会在新进程中被关闭。</p>
<div class="blog_h3"><span class="graybg">复制进程映像</span></div>
<p>系统调用fork允许创建以当前进程为模板，<span style="background-color: #c0c0c0;">复制</span>出一个新的进程。fork调用会在进程表中创建一个新的表项，其中很多属性都和原进程相同，包括所执行的代码。但<span style="background-color: #c0c0c0;">新进程具有自己的数据空间、环境、文件描述符</span>。fork函数的原型如下：</p>
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
pid_t fork( void );</pre>
<p>fork()函数<span style="background-color: #c0c0c0;">很巧妙，其具有“两次返回”的效果</span>，实质上对应了父子进程的不同执行路径。对于父进程，fork()返回子进程的ID；而对于子进程，fork()总是返回0。通过这一特点，可以判断当前执行的代码是父进程还是子进程：</p>
<pre class="crayon-plain-tag">pid_t new_pid;
new_pid = fork();
switch ( new_pid )
{
    case -1 :   /* 错误 */
        break;
    case 0 :    /* 子进程 */
        break;
    default :   /* 父进程 */
        break;
}</pre>
<p>结合fork、exec函数，创建新进程的条件就完备了。</p>
<div class="blog_h3"><span class="graybg">等待进程结束</span></div>
<p>当fork启动一个子进程后，子进程就有了自己的生命周期，并将独立运行，有时候，需要知道子进程何时结束，可以在父进程中用wait系统调用：</p>
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;sys/wait.h&gt;
/**
 * 暂停父进程的执行，直到其子进程结束
 * @param stat_loc 存放状态信息，用于了解子进程的退出状态（即子进程main函数的返回值或者exit函数的退出码）
 *     WIFEXITED(stat_val)     如果子进程正常结束，则取值非0
 *     WEXITSTATUS(stat_val)   如果WIFEXITED非0，取值子进程的退出码
 *     WIFSIGNALED(stat_val)   如果子进程死于未捕获的信号，则取值非0
 *     WTERMSIG(stat_val)      如果WIFSIGNALED非0，取值目标信号的代码
 *     WIFSTOPPED(stat_val)    如果子进程意外终止，取值非0
 *     WSTOPSIG(stat_val)      如果WIFSTOPPED非0，返回信号代码
 *
 * @return 子进程的PID
 */
pid_t wait( int *stat_loc );

/**
 * 等到某个特定的子进程结束
 * @param pid 子进程的PID，如果指定为-1将返回任一子进程的信息
 * @param stat_loc 如果不是空指针，则用来存放状态信息
 * @param option 用于定制waitpid的行为
 */
pid_t waitpid( pid_t pid, int *stat_loc, int options );

/**
 * 检查某个子进程是否结束，立即返回
 * 
 * @return 如果目标子进程没有结束或者意外终止，返回0；否则返回子进程PID
 *         如果函数调用失败，返回-1并设置errno
 */
waitpid(child_pid, (int *) 0, WNOHANG);</pre>
<div class="blog_h2"><span class="graybg">僵尸进程</span></div>
<p>使用fork创建的子进程终止时，其与父进程的关联还会保持，直到<span style="background-color: #c0c0c0;">父进程也正常终止或者父进程调用wait</span>才会结束。在此之前，虽然子进程已经无事可做，但是其在进程表中的项不会被删除，其退出码需要被保存，以备后续父进程的wait调用，这种进程被称为僵尸进程（Zombie，也称为死defunct进程）。</p>
<p>如果父进程<span style="background-color: #c0c0c0;">异常终止</span>，子进程自动把PID=1的进程（init）作为自己的父进程，这类僵尸进程会一直保存在进程表中直到init发现并释放它。</p>
<div class="blog_h1"><span class="graybg"><a id="session"></a>会话</span></div>
<p>在Linux中，会话（Session）通常是指Shell会话，即会话的概念和Shell是分不开的。所谓Shell是用户访问Linux系统的接口，是用户与内核之间的桥梁。</p>
<p>在大部分的Linux发行版中，BASH是默认的Shell实现。每当你：</p>
<ol>
<li>本地打开Terminal窗口</li>
<li>通过SSH远程登录</li>
</ol>
<p>时，一个关联的新会话会被自动创建。不管本地还是远程登录，<span style="background-color: #c0c0c0;">用户都会得到一个与终端（Terminal）相关联的（Shell）进程，该进程作为Session Leader</span>，会话的ID就是该进程的PID。</p>
<p>你也可以编程式的创建新的会话，调用<pre class="crayon-plain-tag">pid_t setsid(void) </pre> 函数可以让当前进程（不管它是否为Shell）作为Session Leader，创建新的会话。如果当前进程已经是Session Leader则会出错。新的会话中只有一个进程，并且它没有关联终端，因此你需要对其输入、输出进行重定向。</p>
<div class="blog_h2"><span class="graybg">会话的终止</span></div>
<p>当终端被挂断（Hangup），即：</p>
<ol>
<li>对于本地Terminal，窗口被关闭</li>
<li>对于SSH终端，网络连接被断开</li>
</ol>
<p>时，Session Leader会接收到SIGHUP信号而退出。在Session Leader退出前，它会<span style="background-color: #c0c0c0;">向所有子进程也发送SIGHUP信号</span>，通常会导致会话中所有进程都结束掉。要想让某个子进程超越Session的生命周期而存活，你可以：</p>
<ol>
<li>让Session Leader主动退出，一般来说就是在Shell里调用exit/logout等命令。默认情况（Shell选项huponexit=off）下，主动退出不会发送SIGHUP给子进程</li>
<li>守护进程化：即所谓Double fork技巧，让Shell的子进程fork出孙子进程，在孙子进程中执行程序逻辑，而子进程立即退出（孙子进程变成孤儿进程）。由于Shell<span style="background-color: #c0c0c0;">只会将SIGHUP发送给直接子进程</span>，孙子进程就不会受到影响了</li>
<li>使用<pre class="crayon-plain-tag">setsid()</pre> 系统调用，让某个子进程变成新的Session的Leader，自立门户</li>
</ol>
<div class="blog_h2"><span class="graybg">进程组</span></div>
<p>顾名思义，进程组（Process group）就是包含了1~N个进程的分组。进程组的主要作用是利于信号的分发——当信号发送给进程组时，其内部的所有进程都会接收到信号。</p>
<p>一个会话中可以包含1~N个进程组，进程组不允许跨会话迁移，进程没有资格创建属于其它会话的进程组，进程也不能加入属于其它会话的进程组。</p>
<div class="blog_h2"><span class="graybg">终端</span></div>
<p>一个Session有且只有一个<a href="/io-faq#terminal">终端</a>，该终端称为控制终端（controlling terminal），改变Session关联的终端这一操作，只能由Session Leader完成。</p>
<p>终端的生命周期可能：</p>
<ol>
<li>与Session相同，这类终端是随着会话而创建的伪终端</li>
<li>与系统相同，这类终端是随着系统启动的，<span style="background-color: #c0c0c0;">init进程在会话结束后重启getty来监听该终端</span></li>
</ol>
<div class="blog_h1"><span class="graybg">信号</span></div>
<div class="blog_h2"><span class="graybg">信号的工作机制</span></div>
<p>软中断信号（简称信号，signal）用来通知进程发生了异步事件（通常是某种错误）。信号是进程之间（包括用户进程之间、用户进程与内核进程之间）进行通信的一种简单方式，信号不会给目标进程发送任何数据。</p>
<p>使用信号并挂起程序（例如pause调用）是Linux程序设计的一个重要部分，这意味着程序不需要总是在执行，在一个无限循环中检查某个事件是否发生，相反，它可以等待事件的发生。这种机制对于只有一个CPU的多任务环境非常重要，进程共享一个处理器，繁忙的循环会对系统性能造成极大的影响。</p>
<div class="blog_h3"><span class="graybg">信号产生时机</span></div>
<p>尽管用户可以通过命令手工发出信号，但是内核是主要的信号发出者。以下场景下会目标进程会收到信号：</p>
<ol>
<li>检测到一个可能出现的硬件故障，如电源故障</li>
<li>程序出现异常行为，比如尝试访问进程外部内存空间、尝试写入只读内存区域</li>
<li>用户从终端向目标程序发出某些指令（ctrl + z、ctrl + c等），终端进程会接收到对应信号</li>
<li>进程的一个子进程终止时</li>
</ol>
<div class="blog_h3"><span class="graybg">信号如何发送给目标进程</span></div>
<p>在进程的进程表项（内核通过进程表对进程进行管理，每个进程在进程表中占有一项）中，有一个<span style="background-color: #c0c0c0;">信号域</span>，信号域的<span style="background-color: #c0c0c0;">每一个slot对应一种信号</span>，对于同一种信号，进程无法知道在处理前来过多少个。</p>
<p>内核通过设置进程的信号域对应slot，来给进程发送信号。</p>
<p>当内核将信号发送给正在睡眠的进程时：</p>
<ol>
<li>如果进程处于可中断的睡眠状态，则唤醒之</li>
<li>如果进程处于不可中断的睡眠状态：则仅仅设置slot</li>
</ol>
<div class="blog_h3"><span class="graybg">进程何时检查是否收到信号</span></div>
<p>进程在以下场景下检查自己是否收到信号：</p>
<ol>
<li>进程即将从内核态返回到用户态时：进程在内核态运行时，信号不起作用，直到其将要返回用户态时才进行处理，信号处理函数在进程上下文中进行</li>
<li>进程即将进入或离开一个适当的低调度优先级睡眠状态时</li>
</ol>
<div class="blog_h3"><span class="graybg">进程如何处理收到的信号</span></div>
<p>进程接收到信号后，可以：</p>
<ol>
<li>忽略该信号</li>
<li>捕获该信号：当前进程继续执行前，调用一个用户定义的函数。这种处理机制类似于中断处理程序</li>
<li>让内核执行与该信号相关的默认动作，大部分信号的默认动作导致进程终止</li>
</ol>
<div class="blog_h2"><span class="graybg">谁接收信号</span></div>
<p>根据POSIX标准，<span style="background-color: #c0c0c0;">信号为进程而产生，但是仅仅其中一个线程可以接收信号并处理</span>。至于哪个线程负责处理信号，取决于实现。</p>
<p>对于Linux：</p>
<ol>
<li>信号可以针对进程的整体而产生，例如kill命令产生的信号</li>
<li>信号可以针对特定线程，例如SIGSEGV、SIGFPE信号，或者pthread_kill命令产生的信号</li>
<li>对于针对进程整体的信号，任何没有被信号阻塞的线程都可以处理之。如果有多个这样的线程（has the signal unblocked），内核会随机的选择一个</li>
</ol>
<p>对于pthreads：每个线程具有独立的信号栈设置（signal stack settings），但是新线程总是从父线程拷贝此设置</p>
<div class="blog_h2"><span class="graybg">常见信号列表（Ubuntu 14）</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;"> 信号</td>
<td style="width: 30px; text-align: center;"> No.</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td> SIGHUP</td>
<td>1</td>
<td>
<p>如果进程通过终端运行，而终端忽然关闭后，进程将收到该信号。HUP是hang up的简写</p>
<p>在终端被关闭时，交互式的Shell会重新发送SIGHUP信号给所有任务（Jobs），不管是运行中的还是挂起（Stopped）的。挂起的任务还会收到SIGCONT信号，确保它们会处理SIGHUP。要阻止向某个任务发送SIGHUP，可以对其调用disown命令</p>
<p>对于BASH等Shell，调用exit、logout时，是否向所有任务发送SIGHUP取决于Shell选项：<pre class="crayon-plain-tag">shopt | grep huponexit</pre> ，该选项默认值是off，即不发送</p>
</td>
</tr>
<tr>
<td> SIGINT</td>
<td>2 </td>
<td>进程被中断（<span style="background-color: #c0c0c0;">int</span>errupted），当通过终端按ctrl + c导致进程接收到该信号</td>
</tr>
<tr>
<td> SIGQUIT</td>
<td>3 </td>
<td>与SIGINT类似，只是该信号是由ctrl + \，该信号会在终结进程时生成core dump（核心转储，即进程的内存映像，可以后续分析）</td>
</tr>
<tr>
<td> SIGILL</td>
<td>4 </td>
<td>非法（<span style="background-color: #c0c0c0;">Ill</span>egal）指令。程序执行了CPU无法理解的机器码时将收到该信号</td>
</tr>
<tr>
<td> SIGTRAP</td>
<td>5 </td>
<td>主要用于调试和程序跟踪</td>
</tr>
<tr>
<td> SIGABRT</td>
<td>6 </td>
<td>程序调用abort()函数时触发，导致程序紧急停止 </td>
</tr>
<tr>
<td> SIGBUS</td>
<td>7 </td>
<td>尝试以错误的方式访问内存时触发</td>
</tr>
<tr>
<td> SIGFPE</td>
<td>8 </td>
<td>程序中出现浮点数异常（<span style="background-color: #c0c0c0;">f</span>loating <span style="background-color: #c0c0c0;">p</span>oint <span style="background-color: #c0c0c0;">e</span>xception）时触发</td>
</tr>
<tr>
<td> SIGKIL</td>
<td>9 </td>
<td>立即终止进程，该信号不能被忽略。可以由ctrl + c引发</td>
</tr>
<tr>
<td> SIGUSR1</td>
<td>10 </td>
<td>供编程人员使用</td>
</tr>
<tr>
<td> SIGSEGV</td>
<td>11 </td>
<td>段错误，无效内存段访问，尝试越界访问内存（不是分配给当前进程的内存）时触发</td>
</tr>
<tr>
<td> SIGUSR2</td>
<td>12 </td>
<td>供编程人员使用</td>
</tr>
<tr>
<td> SIGPIPE</td>
<td>13 </td>
<td>当进程通过管道机制，将信息输出给目标进程的输入时，目标进程挂掉，当前进程收到此信号</td>
</tr>
<tr>
<td> SIGALRM</td>
<td>14 </td>
<td>进程调用alarm()函数，定时器到期后，系统通过该信号提示进程</td>
</tr>
<tr>
<td> SIGTERM</td>
<td>15 </td>
<td>这是一个一般的、用于“礼貌的”终结进程的信号。与SIGKIL不同，该信号可能被阻塞、处理或者忽略</td>
</tr>
<tr>
<td> SIGCHLD</td>
<td>17 </td>
<td>进程先前通过fork() 创建了子进程，这些子进程中的一个或者多个挂掉时，父进程收到此信号 </td>
</tr>
<tr>
<td> SIGCONT</td>
<td>18 </td>
<td>可以使由SIGSTOP导致休眠的进程恢复 </td>
</tr>
<tr>
<td> SIGSTOP</td>
<td>19 </td>
<td>如果系统发送该信号给进程，进程的状态将被保存，并且不再获得CPU周期</td>
</tr>
<tr>
<td> SIGTSTP</td>
<td>20 </td>
<td>Terminal SToP。本质上与SIGSTOP一样，该信号由终端操作ctrl + z导致</td>
</tr>
<tr>
<td> SIGTTIN</td>
<td>21 </td>
<td>当后台运行的进程尝试从stdin读取数据时，系统发送该信号给它。目标进程的典型响应是进入暂停，一直到进入前台时，SIGCONT信号到达 </td>
</tr>
<tr>
<td> SIGTTOU</td>
<td>22 </td>
<td>类似于SIGTTIN，当后台进程尝试写数据到stdout时触发</td>
</tr>
<tr>
<td> SIGURG</td>
<td>23 </td>
<td>带外数据（out-of-band，OOB）到达时，使用网络连接的进程接收到该信号。带外数据不使用与普通数据相同的通道。对于TCP协议，由于没有所谓带外通道，是通过URG位实现的。带外数据通常是一些紧急的重要数据</td>
</tr>
<tr>
<td> SIGXCPU</td>
<td>24 </td>
<td>系统发送该信号到使用CPU到达限制的进程 </td>
</tr>
<tr>
<td> SIGXFSZ</td>
<td>25 </td>
<td>系统发送该信号到尝试创建超过尺寸限制的文件的进程</td>
</tr>
<tr>
<td> SIGVTALRM</td>
<td>26 </td>
<td>与SIGALRM类似，但不通过真实时间计时，而是通过目标进程使用的的CPU时间计时</td>
</tr>
<tr>
<td> SIGPROF</td>
<td>27 </td>
<td>与SIGVTALRM类似，但是计时除了目标进程使用的CPU时间，而包括为了目标进程服务的系统代码执行时间</td>
</tr>
<tr>
<td> SIGIO</td>
<td>29 </td>
<td>亦即SIGPOLL。当有输入等待进程处理，或者输出通道可以供进程写入时，系统给进程发出该信号</td>
</tr>
<tr>
<td> SIGPWR</td>
<td>30 </td>
<td>当切换到紧急备用电源时，进程接收到该信号</td>
</tr>
<tr>
<td> SIGSYS</td>
<td>31 </td>
<td>未使用</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Linux信号相关API</span></div>
<div class="blog_h3"><span class="graybg">处理信号</span></div>
<p>通过signal可以设置信号的处理函数：</p>
<pre class="crayon-plain-tag">#include &lt;signal.h&gt;
/**
 * 设置信号的处理函数
 * @param sig 信号
 * @param func 处理回调函数
 * @return 返回先前的信号处理函数的指针，如果未定义信号处理函数返回SIG_ERR并设置errno为正数
 *         如果给出一个无效的信号，或者尝试处理不可捕获、不可忽略的信号（例如SIGKILL），则将errno设置为EINVAL
 */
typedef void (*signal_handler_t )( int );
signal_handler_t signal( int sig, signal_handler_t );

//两个特殊的信号处理函数：
// SIG_IGN 忽略信号
// SIG_DFL 恢复此信号的默认处理行为

#include &lt;unistd.h&gt;
/**
 * 导致当前进程暂停执行，直到接收到一个信号
 * 当暂停被一个信号中断时，返回-1并且设置errno为EINTR
 */
int pause( void );</pre>
<p>信号处理函数中调用某些函数是不安全的，例如printf，最好是在信号处理函数中设置一个标记，然后在主程序中检查标记再调用某些函数。下面是信号处理函数的例子：</p>
<pre class="crayon-plain-tag">#include &lt;signal.h&gt;
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
void ouch( int sig )
{
    printf( "OUCH! - I got signal %d\n", sig );
    //该信号处理函数恢复默认行为:停止程序
    signal( SIGINT, SIG_DFL );
}

int main()
{
    //进程启动后，设置信号的处理函数
    signal( SIGINT, ouch );
    while ( 1 )
    {
        //此循环会不停执行，除非接收到信号
        printf( "Hello World!\n" );
        sleep( 1 );
    }
}</pre>
<p>X/Open和UNIX规范推荐了更加健壮的信号编程接口：</p>
<pre class="crayon-plain-tag">#include &lt;signal.h&gt;
/**
 * 指定接收到sig信号后采取的动作
 * @param sig 处理的信号
 * @param act 需要指向的动作
 * @param oact 如果不为空，此函数调用前sig信号的处理动作被转储到该指针
 * @return 如果成功，返回0，失败返回-1，如果给出的信号无效或者对不允许忽略或者
 *         捕获的信号进行忽略或者捕获，则设置errno=EINVAL
 */
int sigaction( int sig, const struct sigaction *act, struct sigaction *oact );
typedef  void (*sa_handler_t)( int );
struct sigaction
{
    sa_handler_t sa_handler; //信号处理函数，包括SIG_DFL、SIG_IGN
    //一个信号集，在sa_handler被调用之前，此信号集中的信号不会传递给进程，
    //可以防止信号处理函数尚未执行完毕就接收到新信号并重入信号处理函数的情况
    //信号处理函数在指向过程中，可能被新的信号中断而再次调用，这不仅仅是递归调用
    //的问题，更牵涉到可重入（安全的进入和再次指向）的问题
    sigset_t sa_mask;
    //标记位：
    //SA_RESETHAND 表示处理函数调用后（入口第一句后），即重置默认处理函数（SIG_DFL）
    //SA_NOCLDSTOP 子进程停止后不产生SIGCHLD信号
    //SA_RESTART   重启可中断函数而不是给出EINTR错误。许多系统调用是可中断的，也就是
    //             说接收到信号后系统调用会返回一个错误并设置errno=EINTR
    //             以表示该调用因为信号而返回。设置该标记后，调用将重启而不是被信号中断
    //SA_NODEFER   捕获到信号时不将其加入到信号屏蔽掩码中。通常的做法：为防止同一信号不
    //             断到达，新接收到的信号会被加入到掩码中，直到处理函数指向完毕
    int sa_flags;
};

//向信号集中增加一个信号
int sigaddset( sigset_t *set, int signo );
//创建空白信号集
int sigemptyset( sigset_t *set );
//创建包含所有已定义信号的信号集
int sigfillset( sigset_t *set );
//从信号集中删除指定的信号
int sigdelset( sigset_t *set, int signo );
//判断信号是否为信号集的成员，如果是返回1否则返回0，如果给定的信号无效返回-1并设置EINVAL
int sigismember( sigset_t *set, int signo );
/**
 * 根据how指定的方式修改进程的信号屏蔽掩码
 * @param how
 *        SIG_BLOCK    将set加入到进程的掩码
 *        SIG_SETMASK  将进程的掩码设置为set
 *        SIG_UNBLOCK  从进程的掩码中删除set
 * @param set 新的信号屏蔽掩码，如果设置为空，仅仅是把当前信号屏蔽掩码保存到oset
 * @param oset 原先的信号屏蔽掩码
 * @return 如果成功返回0；如果how无效返回-1并设置errno=EINVAL
 *
 */
int sigprocmask( int how, const sigset_t *set, sigset_t *oset );

/**
 * 如果一个信号被进程阻塞，就不会传递给进程而停留在待处理状态。
 * 该函数可以查看阻塞的信号中那些处于待处理状态
 */
int sigpending( sigset_t *set );

/**
 * 挂起进程自己，等待信号集中某个信号到达
 * 如果接收到的信号终止了程序，该调用不会返回；否则返回-1并设置errno=EINTR
 */
int sigsuspend( const sigset_t *sigmask );</pre>
<p>下面是对前一个信号处理例子的改写：</p>
<pre class="crayon-plain-tag">#include &lt;signal.h&gt;
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
void ouch( int sig )
{
    printf( "OUCH! - I got signal %d\n", sig );
}
int main()
{
    struct sigaction act;
    act.sa_handler = ouch; //设置信号处理函数
    sigemptyset( &amp;act.sa_mask ); //创建空的信号屏蔽掩码
    act.sa_flags = SA_RESETHAND;
    sigaction( SIGINT, &amp;act, 0 );
    while ( 1 )
    {
        printf( "Hello World!\n" );
        sleep( 1 );
    }
}</pre>
<p>信号处理函数中会遇到可重入问题，下表列出可以被信号处理函数安全调用的函数，他们本身是可重入、或者本身不会再生成信号：<img class="size-full wp-image-6937 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2009/08/safe-func-in-sig-hdl.png" alt="safe-func-in-sig-hdl" width="98%" /></p>
<div class="blog_h3"><span class="graybg">发送信号</span></div>
<p>进程可以调用kill函数来向<span style="background-color: #c0c0c0;">包括其自身在内的进程</span>发送一个信号。如果进程没有发送目标信号的权限，对kill的调用就会失败，失败的常见原因是目标进程是由另外一个用户所拥有。下面的函数可以用来发送信号：</p>
<pre class="crayon-plain-tag">#include &lt;sys/types.h&gt;
#include &lt;signal.h&gt;
/**
 * 将信号发送给指定进程，如果成功返回0，失败返回-1并设置errno，errno可以为以下值：
 * EINVAL  给定的信号无效
 * EPERM   发送进程的权限不够，一般只能发送给同一用户的进程，超级用户可以发送信号给所有进程
 * ESRCH   目标进程不存在
 *
 */
int kill( pid_t pid, int sig );

#include &lt;unistd.h&gt;
/**
 * 在延迟seconds秒以后，发送一个SIGALRM信号
 * 每个进程只能有一个闹钟，因此后续的调用将导致重新计时并返回上一次闹钟设置的剩余秒数
 * 如果seconds设置为0则取消闹钟请求
 */
unsigned int alarm( unsigned int seconds );</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/linux-signals-processes-and-sessions">Linux信号、进程和会话</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/linux-signals-processes-and-sessions/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>POSIX线程编程</title>
		<link>https://blog.gmem.cc/pthread-programming</link>
		<comments>https://blog.gmem.cc/pthread-programming#comments</comments>
		<pubDate>Thu, 02 Jul 2009 08:09:16 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[并发编程]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=6942</guid>
		<description><![CDATA[<p>基本知识 什么是线程 一个程序中可以有多个代码指向序列，这每个序列就是一个线程（Thread），线程是进程内部的一个控制序列。一个进程至少具有一个执行线程。 Linux中通过fork创建的新进程，与Phtead API创建的线程是具有很大不同的： fork出的进程，拥有自己的变量、PID，其时间调度是独立的，其执行几乎完全独立于父进程 进程中创建的新线程，具有自己的栈（因而具有独立的局部变量），但是它与创建者共享全局变量、文件描述符、信号处理函数、当前目录状态 尽管线程已经出现很长时间，但是在IEEE POSIX委员会发布相关标准前，并没有在UNIX系统中得到广泛的支持。POSIX 1003.1c规范改变了这一状况，线程的实现被标准化，绝大部分的Linux发行版都支持它。 Linux在1996年开始支持线程，当时的函数库称为LinuxThread，该线程实现和POSIX标准存在细微的差别，特别是信号处理的相关部分。许多项目致力于改善Linux对线程的支持，以清除与POSIX标准的差异、增加性能，其中大部分工作集中在如何将用户级线程映射到内核级线程。这些项目中，以下一代POSIX线程（NGPT）和本地POSIX线程库（NPTL）为代表，而NPTL成为了Linux线程库的新标准。 线程的优缺点 尽管相比起某些其它OS，Linux创建进程的效率很高，但是创建新线程比创建新进程的代价要小得多。 线程具有以下优势场景： 有时，一个程序需要“同时”做几件事情。例如：编辑文档的时候同时进行单词个数的统计；数据库软件需要同时服务多个连接，这些连接可以对应不同的线程，线程之间需要紧密协作才能完成加锁、数据一致性要求，这些需求通过线程很容易实现 混杂着输入、计算、输出的应用程序。分离为多个线程来处理，可以有效改善程序的性能。需要服务多个客户端的网络服务器也是天生适合多线程的例子 一般而言，线程之间的切换需要操作系统做的工作比进程切换少得多。多个线程对资源的需求要远远小于多个进程 线程的主要缺点是： 多线程程序需要非常仔细的设计。在多线程程序中，因为时序上的细微差别、无意间的变量共享引发错误的可能性很大 多线程程序的调试比单线程困难的多，因为线程之间的交互非常难于控制 对于计算密集型程序，将计算拆分到多个线程运行，对于单处理器来说没有价值 pthread编程基础 <a class="read-more" href="https://blog.gmem.cc/pthread-programming">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/pthread-programming">POSIX线程编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">基本知识</span></div>
<div class="blog_h3"><span class="graybg">什么是线程</span></div>
<p>一个程序中可以有多个代码指向序列，这每个序列就是一个线程（Thread），<span style="background-color: #c0c0c0;">线程是进程内部的一个控制序列</span>。一个进程至少具有一个执行线程。</p>
<p>Linux中通过fork创建的新进程，与Phtead API创建的线程是具有很大不同的：</p>
<ol>
<li>fork出的进程，拥有<span style="background-color: #c0c0c0;">自己的变量、PID</span>，其时间调度是独立的，其执行几乎完全独立于父进程</li>
<li>进程中创建的新线程，具有<span style="background-color: #c0c0c0;">自己的栈（因而具有独立的局部变量</span>），但是它与创建者<span style="background-color: #c0c0c0;">共享全局变量、文件描述符、信号处理函数、当前目录状态</span></li>
</ol>
<p>尽管线程已经出现很长时间，但是在IEEE POSIX委员会发布相关标准前，并没有在UNIX系统中得到广泛的支持。POSIX 1003.1c规范改变了这一状况，线程的实现被标准化，绝大部分的Linux发行版都支持它。</p>
<p>Linux在1996年开始支持线程，当时的函数库称为LinuxThread，该线程实现和POSIX标准存在细微的差别，特别是信号处理的相关部分。许多项目致力于改善Linux对线程的支持，以清除与POSIX标准的差异、增加性能，其中大部分工作集中在如何<span style="background-color: #c0c0c0;">将用户级线程映射到内核级线程</span>。这些项目中，以下一代POSIX线程（NGPT）和<span style="background-color: #c0c0c0;">本地POSIX线程库（NPTL）</span>为代表，而NPTL成为了Linux线程库的新标准。</p>
<div class="blog_h3"><span class="graybg">线程的优缺点</span></div>
<p>尽管相比起某些其它OS，<span style="background-color: #c0c0c0;">Linux创建进程的效率很高</span>，但是<span style="background-color: #c0c0c0;">创建新线程比创建新进程的代价要小得多</span>。</p>
<p>线程具有以下优势场景：</p>
<ol>
<li>有时，一个程序需要“同时”做几件事情。例如：编辑文档的时候同时进行单词个数的统计；数据库软件需要同时服务多个连接，这些连接可以对应不同的线程，线程之间需要紧密协作才能完成加锁、数据一致性要求，这些需求通过线程很容易实现</li>
<li>混杂着输入、计算、输出的应用程序。分离为多个线程来处理，可以有效改善程序的性能。需要服务多个客户端的网络服务器也是天生适合多线程的例子</li>
<li>一般而言，<span style="background-color: #c0c0c0;">线程之间的切换需要操作系统做的工作比进程切换少得多</span>。多个线程对资源的需求要远远小于多个进程</li>
</ol>
<p>线程的主要缺点是：</p>
<ol>
<li>多线程程序需要非常仔细的设计。在多线程程序中，因为时序上的细微差别、无意间的变量共享引发错误的可能性很大</li>
<li>多线程程序的调试比单线程困难的多，因为线程之间的交互非常难于控制</li>
<li>对于计算密集型程序，将计算拆分到多个线程运行，对于单处理器来说没有价值</li>
</ol>
<div class="blog_h2"><span class="graybg">pthread编程基础</span></div>
<p>Linux下线程具有一套完整的库函数，这些函数大部分以pthread_开头，声明在头文件：<pre class="crayon-plain-tag">pthread.h</pre> 中，链接时需要指定-lpthread。</p>
<p>在最初设计UNIX/POSIX例程时，人们往往假设每个进程只有一个执行线程，典型的例子就是errno全局变量。在多线程环境下，需要“可重入例程”：即多次调用仍然能正常工作，这些调用可能来自不同的线程，也可能是某种形式的递归调用。</p>
<p>编写多线程程序时，通常需要<span style="background-color: #c0c0c0;">在任何include语句之前</span>定义宏<pre class="crayon-plain-tag">_REENTRANT</pre> ，以启用“可重入”功能，它将做以下三件事情：</p>
<ol>
<li>对部分函数进行重新定义，定义为其可重入版本，这些函数的名字一般不会发生改变，只是在原函数的名字后面加上<pre class="crayon-plain-tag">_r</pre> </li>
<li>stdio.h中原来以宏实现的一些函数，变成可重入函数</li>
<li>errno.h中定义的变量errno变成一个函数调用，能够以多线程的形式来获取真正的errno值</li>
</ol>
<p>下面是基本的线程API：</p>
<pre class="crayon-plain-tag">#include &lt;pthread.h&gt;
/**
 * 创建一个新的线程
 * @param thread 指向pthread_t的指针，线程被创建时，指针指向的变量被写入一个标识符，该标识符用来引用新的线程
 * @param attr 设置线程的属性，不需要时设置为NULL
 * @param start_routine 线程启动时执行的函数
 * @param arg 线程启动时指向的函数的参数
 *
 * @return 成功时返回0，错误时返回错误代码，pthread_函数的大部分都没有遵循UNIX函数惯例——失败时返回-1
 */
int pthread_create( pthread_t *thread, pthread_attr_t *attr,
        void *(*start_routine)( void * ), void *arg );

/**
 * 终止一个线程的执行（谁调用，谁被终止），类似于exit()终止一个进程的执行
 * @param retval 存放返回值的指针，注意不能使用局部变量，因为线程退出后已经销毁
 */
void pthread_exit( void *retval );

/**
 * 类似于进程中用来收集子进程信息的wait函数
 * @param th 将要等待的线程
 * @param thread_return 指向一个指针，该指针指向线程的返回值
 */
int pthread_join( pthread_t th, void **thread_return );

/**
 * 可以让一个线程要求另外一个线程终止，就好像进程之间发送信号一样。
 * @param thread 需要终止的线程的标识符
 */
int pthread_cancel( pthread_t thread );
/**
 * 可以设置自己的取消状态
 * @param state  PTHREAD_CANCEL_ENABLE 允许取消请求（默认）；PTHREAD_CANCEL_DISABLE 忽视取消请求
 */
int pthread_setcancelstate( int state, int *oldstate );
/**
 * 可以设置自己的取消方式
 * @param type
 *    PTHREAD_CANCEL_ASYNCHRONOUS 接收到请求后立即采取行动；
 *    PTHREAD_CANCEL_DEFERRED 延迟取消行为（默认），直到线程执行了以下函数之一：
 *      pthread_join, pthread_cond_wait,pthread_cond_timedwait, pthread_testcancel, sem_wait, sigwait
 */
int pthread_setcanceltype( int type, int *oldtype );</pre>
<p>下面是一个例子：</p>
<pre class="crayon-plain-tag">#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;pthread.h&gt;
void *thread_function( void *arg );
//一个共享变量
char message[] = "Hello";
int main()
{
    setbuf( stdout, _IOFBF );
    int res;
    pthread_t a_thread;
    void *thread_result; //存放线程返回值的指针
    //创建一个线程
    res = pthread_create( &amp;a_thread, NULL, thread_function, ( void * ) message );
    if ( res != 0 )
    {
        perror( "Thread creation failed" );
        exit( EXIT_FAILURE );
    }
    printf( "Waiting for thread to finish...\n" );
    //等待线程运行结束，将返回值指针的地址传递给函数，线程指向完毕后，会填写此地址
    res = pthread_join( a_thread, &amp;thread_result );
    if ( res != 0 )
    {
        perror( "Thread join failed" );
        exit( EXIT_FAILURE );
    }
    //打印线程的返回值
    printf( "Thread joined, it returned message: %s\n", ( char * ) thread_result );
    //打印共享变量
    printf( "Message changed: %s\n", message );
    exit( EXIT_SUCCESS );
}
//线程入口函数定义
void *thread_function( void *arg )
{
    printf( "Thread is running. Argument: %s\n", ( char * ) arg );
    sleep( 3 );
    //修改共享变量
    strcpy( message, "Bye" );
    //线程退出，返回一段文本
    pthread_exit( "Thread exit" );
}</pre>
<div class="blog_h3"><span class="graybg">线程属性</span></div>
<p>在创建线程的时候，允许指定一个属性参数，下面的API用于控制属性：</p>
<pre class="crayon-plain-tag">#include &lt;pthread.h&gt;
/**
 * 初始化一个线程属性
 */
int pthread_attr_init( pthread_attr_t *attr );
/**
 * 销毁一个线程属性，一旦被销毁，除非再次初始化，否则不能继续使用
 */
int pthread_attr_destroy( pthread_attr_t * );

//线程属性控制函数
//detachstate：允许无需对线程进行rejoin
//detachstate可以为：PTHREAD_CREATE_JOINABLE（默认）、PTHREAD_CREATE_DETACHED，后者不允许针对其pthread_join调用
int pthread_attr_setdetachstate( pthread_attr_t *attr, int detachstate );
int pthread_attr_getdetachstate( const pthread_attr_t *attr, int *detachstate );
//schedpolicy：控制线程如何被调度。可选值：SCHED_OTHER（默认）、SCHED_RP、SCHED_FIFO
//后两个值要求超级用户权限，分别使用循环调度、先进先出调度策略
int pthread_attr_setschedpolicy( pthread_attr_t *attr, int policy );
int pthread_attr_getschedpolicy( const pthread_attr_t *attr, int *policy );
//scheparam：与schedpolicy配合使用，可以控制SCHED_OTHER的行为
int pthread_attr_setschedparam( pthread_attr_t *attr, const struct sched_param *param );
int pthread_attr_getschedparam( const pthread_attr_t *attr, struct sched_param *param );
//inheritsched：是否从创建者继承调度属性。可选值：PTHREAD_EXPLICIT_SCHED、PTHREAD_INHERIT_SCHED
//分别表示需要明确设定，以及从创建者继承
int pthread_attr_setinheritsched( pthread_attr_t *attr, int inherit );
int pthread_attr_getinheritsched( const pthread_attr_t *attr, int *inherit );
//scope：控制线程调度的计算方式，Linux目前仅仅支持PTHREAD_SCOPE_SYSTEM
int pthread_attr_setscope( pthread_attr_t *attr, int scope );
int pthread_attr_getscope( const pthread_attr_t *attr, int *scope );
//stacksize：控制线程栈大小，单位字节
int pthread_attr_setstacksize( pthread_attr_t *attr, int scope );
int pthread_attr_getstacksize( const pthread_attr_t *attr, int *scope );</pre>
<p>下面是一个控制线程优先级的例子：</p>
<pre class="crayon-plain-tag">#include &lt;pthread.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;sys/sched.h&gt;

int main( int argc, char **argv )
{
    pthread_attr_t thread_attr;
    int max_priority;
    int min_priority;
    struct sched_param scheduling_value;

    int res = pthread_attr_setschedpolicy( &amp;thread_attr, SCHED_OTHER );
    if ( res != 0 )
    {
        perror( "Setting scheduling policy failed" );
        exit( EXIT_FAILURE );
    }
    max_priority = sched_get_priority_max( SCHED_OTHER );
    min_priority = sched_get_priority_min( SCHED_OTHER );
    scheduling_value.sched_priority = min_priority; //设置为最低优先级
    res = pthread_attr_setschedparam( &amp;thread_attr, &amp;scheduling_value );
    if ( res != 0 )
    {
        perror( "Setting scheduling priority failed" );
        exit( EXIT_FAILURE );
    }
}</pre>
<div class="blog_h2"><span class="graybg">同步</span></div>
<p>Linux包含一组控制线程执行和<span style="background-color: #c0c0c0;">代码临界区域</span>访问的方法。 </p>
<div class="blog_h3"><span class="graybg">信号量</span></div>
<p>信号量这个概念最初由Dijkstra提出，他是一种特殊的变量，<span style="background-color: #c0c0c0;">可以被增加或减少</span>，即使在多线程环境下，对其进行增加、减少等关键操作也能保证原子性。这意味着多个线程对信号量的操作将被顺序执行，而对于普通变量，来自程序中多个线程的冲突操作造成的结果是不能确定的。</p>
<p>信号量用于控制对一组相同对象的访问。Linux下有两组函数可以用于信号量：</p>
<ol>
<li>一组取自POSIX的扩展，用于线程</li>
<li>一组被称为System V信号量，用于进程的同步</li>
</ol>
<p>Linux可用的信号量包括两种：<span style="background-color: #c0c0c0;">二进制信号量、计数信号量</span>，前者只有0/1两个值，可以用来保护代码同时只能被一个线程访问。线程中使用的基本信号量函数有以下四个：</p>
<pre class="crayon-plain-tag">#include &lt;semaphore.h&gt;
/**
 * 创建一个信号量
 * @param sem 被初始化的信号量
 * @param pshared 信号量的类型，如果为0表示是进程内部信号量，如果为1表示可以在多个进程之间共享
 * @param value 信号量初始值
 */
int sem_init( sem_t *sem, int pshared, unsigned int value );
/**
 * 以原子的方式将信号量的值加1
 * 所谓原子操作是指：如果两个线程同时企图调用该函数，他们不会互相干扰，结果总是一致的加2
 */
int sem_post( sem_t * sem );
/**
 * 以原子的方式将信号量的值减1，但它会等到直到信号量有个非零值时才会指向减法操作
 * 如果对值为0的信号量调用该操作，调用者将一直等待直到信号量不为0为止
 */
int sem_wait( sem_t * sem );
//尝试获取一个信号量，立即返回
int sem_trywait( sem_t *sem );
//等待一个信号量，在指定时间后超时退出
int sem_timedwait( sem_t *sem, const struct timespec *abstime );
/**
 * 在用完信号量后对其进行清理
 */
int sem_destroy( sem_t * sem );</pre>
<div class="blog_h3"><span class="graybg">互斥量</span></div>
<p>用于控制同一时刻只能有一个线程可以访问共享内存，互斥量允许锁住某个对象，直到操作完成后再释放它。</p>
<pre class="crayon-plain-tag">#include &lt;pthread.h&gt;
//这些函数成功返回0，失败返回错误代码，不设置errno
/**
 * 初始化一个互斥量
 * @param mutex 互斥量
 * @param mutexattr 互斥量的属性，可以定制其行为
 */
int pthread_mutex_init( pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr );
/**
 * 锁定一个互斥量，如果互斥量已经被加锁，该调用会一直阻塞直到其解锁
 */
int pthread_mutex_lock( pthread_mutex_t *mutex );
/**
 * 解锁一个互斥量
 */
int pthread_mutex_unlock( pthread_mutex_t *mutex );
/**
 * 销毁一个互斥量
 */
int pthread_mutex_destroy( pthread_mutex_t *mutex );</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/pthread-programming">POSIX线程编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/pthread-programming/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Java线程与并发编程</title>
		<link>https://blog.gmem.cc/java-threads-and-concurrent</link>
		<comments>https://blog.gmem.cc/java-threads-and-concurrent#comments</comments>
		<pubDate>Thu, 27 Nov 2008 13:05:40 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[并发编程]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=8068</guid>
		<description><![CDATA[<p>基本知识 线程的本质是机器指令的执行路径，它完全是多任务操作系统之上的虚拟概念。从硬件角度来看，每一个CPU只有一条执行路径，成百上千的线程同时运行，只是操作系统调度形成的假象。 每一个计算机程序，或者说进程，至少有一个线程，称为主线程。Java程序的主线程叫做main thread，它以某个Class的main()方法为执行路径的起始点。任何计算机程序都是以单一线程开始运行的。 并发编程常用术语 术语 说明  屏障(Barrier) 屏障代表多个线程的集合点，所有线程必须都到达该屏障后，它们才能继续运行下去，因此先到达的线程会陷入等待  条件变量(Condition Variable) 条件变量是和某一个锁（Lock）关联的变量，通常用于同步环境中，实现等待-唤醒机制。线程可以在拥有锁的前提下，等待该锁的某个条件变量；或者在拥有锁的前提下，唤醒一个或者全部正在等待某个条件变量的线程 条件变量也称为事件变量（Event Variable） 临界区域(Critical Section) 代表一个需要同步化的方法或者代码块，所有线程必须串行的经过临界区域。本质上就是一个隐式的锁获取和释放的区域 锁(Lock) 用来表示对进入临界区的特定线程授予的访问权 读写锁（Read/Write Lock）可以允许多个线程同时取得，只要它们都同意只进行“读”操作 监视器(Monitor) <a class="read-more" href="https://blog.gmem.cc/java-threads-and-concurrent">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/java-threads-and-concurrent">Java线程与并发编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">基本知识</span></div>
<p>线程的本质是<span style="background-color: #c0c0c0;">机器指令的执行路径</span>，它完全是多任务操作系统之上的虚拟概念。从硬件角度来看，每一个CPU只有一条执行路径，成百上千的线程同时运行，只是操作系统调度形成的假象。</p>
<p>每一个计算机程序，或者说进程，<span style="background-color: #c0c0c0;">至少有一个线程</span>，称为主线程。Java程序的主线程叫做main thread，它以某个Class的main()方法为执行路径的起始点。任何计算机程序都是以<span style="background-color: #c0c0c0;">单一线程开始</span>运行的。</p>
<div class="blog_h3"><span class="graybg">并发编程常用术语</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;">术语</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>屏障(Barrier)</td>
<td>屏障代表多个线程的集合点，所有线程必须都到达该屏障后，它们才能继续运行下去，因此先到达的线程会陷入等待 </td>
</tr>
<tr>
<td>条件变量(Condition Variable)</td>
<td>
<p>条件变量是和某一个锁（Lock）关联的变量，通常用于同步环境中，实现等待-唤醒机制。线程可以在拥有锁的前提下，等待该锁的某个条件变量；或者在拥有锁的前提下，唤醒一个或者全部正在等待某个条件变量的线程</p>
<p>条件变量也称为事件变量（Event Variable）</p>
</td>
</tr>
<tr>
<td>临界区域(Critical Section)</td>
<td>代表一个需要同步化的方法或者代码块，所有线程必须串行的经过临界区域。本质上就是一个隐式的锁获取和释放的区域</td>
</tr>
<tr>
<td>锁(Lock)</td>
<td>
<p>用来表示对进入临界区的特定线程授予的访问权</p>
<p>读写锁（Read/Write Lock）可以允许多个线程同时取得，只要它们都同意只进行“读”操作</p>
</td>
</tr>
<tr>
<td>监视器(Monitor)</td>
<td>该术语在不同的线程系统中含义有所不同，可能代指Lock，或者代指等待-唤醒机制</td>
</tr>
<tr>
<td>互斥(Mutex)</td>
<td>和Lock类似，但是往往是跨进程的、基于操作系统级别的</td>
</tr>
<tr>
<td>信号量(Semaphore)</td>
<td>线程可以等待一个或者多个信号量的计数，另外一个线程则可以释放一个或者多个信号量的计数，当前一种线程获得足够的计数后，停止等待</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Java线程的创建和管理</span></div>
<div class="blog_h3"><span class="graybg">创建线程</span></div>
<p>Java允许两种创建新线程的风格：</p>
<ol>
<li>继承java.lang.Thread，并覆盖run()方法</li>
<li>实现java.lang.Runnable接口，并将其传递给Thread类的构造器</li>
</ol>
<p>Thread对象<span style="background-color: #c0c0c0;">本质上并不代表线程本身</span>，而是一组与线程有关的方法和数据封装。</p>
<p>Thread的构造函数包括以下参数：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 200px; text-align: center;"> 参数</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>String name</td>
<td>新线程的名称，用于显示信息使用，没有其他特殊意义。默认命名为Thread-N，N为唯一的数字</td>
</tr>
<tr>
<td>Runnable target</td>
<td>新线程需要执行的指令列表，位于该接口的run()方法中</td>
</tr>
<tr>
<td>ThreadGroup group</td>
<td>新线程加入的线程组，默认与调用新线程构造器的那个线程的线程组一致</td>
</tr>
<tr>
<td>long stackSize</td>
<td>新线程执行方法时，存放临时变量的栈的大小</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">线程生命周期方法</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 120px; text-align: center;"> 生命周期阶段</td>
<td style="width: 120px; text-align: center;">相关方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">创建</td>
<td><em>new Thread()</em></td>
<td>
<p>在Java中，线程也是对象，因此它的创建是通过调用构造器完成的，上面的表格详细的介绍了线程构造器的参数。</p>
<p>线程被构造后即存在，但是还没有执行任何代码。在此时，其他线程就可以和新线程进行交互：例如设置优先级、名称、是否守护线程</p>
</td>
</tr>
<tr>
<td style="text-align: center;">启动 </td>
<td>start()</td>
<td>
<p>调用start()方法后，JVM执行一些内部管理工作，然后调用thread的run()方法。</p>
<p>start()方法会立即返回，返回后JVM中多了一个线程，这个新线程将处于活动状态isAlive()==true，直到线程终结之前，alive一直为true</p>
</td>
</tr>
<tr>
<td style="text-align: center;">终结</td>
<td>stop()</td>
<td>
<p>当run()方法执行完毕后——遇到return语句、执行到最后一行、或者抛出异常——线程就会自然终结</p>
<p>stop()方法不能用来强制终结线程，它存在固有的缺陷，已经被废弃不用</p>
</td>
</tr>
<tr>
<td style="text-align: center;">暂停</td>
<td> </p>
<p><em>Thread.sleep()</em><br />join()<br />Object.wait()</p>
<p>suspend()</p>
</td>
<td>
<p>调用Thread.sleep()静态方法，可以让当前线程暂停一段时间，之后自动恢复执行。线程暂停的时间分辨率并不是很高，一般最高精确到几毫秒。该方法不会导致线程放弃持有的监视器</p>
<p>suspend()方法设计用来暂停其他线程，但是存在与stop()一样的缺陷</p>
<p>线程可以调用任意对象的wait()方法，导致它自己陷入等待中，前提是线程持有该对象的监视器，直到被中断或者该对象的notify()、notifyAll()被调用之前，线程会保持暂停，并且放弃持有的监视器</p>
<p>调用一个线程的join()方法，会导致当前线程暂停，直到目标线程终结。如果目标线程已经终结，那么该调用会立即返回</p>
</td>
</tr>
<tr>
<td style="text-align: center;">恢复</td>
<td>
<p><em>Thread.sleep()</em><br />Object.notify()<br />Object.notifyAll()</p>
<p>resume()</p>
</td>
<td>
<p>resume()方法设计用来恢复暂停的线程，同样存在固有缺陷</p>
<p>线程调用任意对象的notify()、notifyAll()方法，会导致某个处于等待中的线程被唤醒，唤醒后的线程会立即尝试获取监视器</p>
</td>
</tr>
<tr>
<td style="text-align: center;">清理</td>
<td> </td>
<td>线程终结后，其Java对象仍然可以被访问，可以附带一些有价值的信息。如果Thread对象脱离作用范围，会被GC回收，回收可能连带着系统资源的清理</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">让线程停止的正确方式</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 120px; text-align: center;">方式 </td>
<td style="width: 120px; text-align: center;">相关方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">设置标记位</td>
<td> </td>
<td>
<p>当前线程以实例变量的形式提供一个标记位，允许其他线程改变这个标记位</p>
<p>当前线程会在一个循环中执行，定期检测标记位，如果标记位被设置，则return</p>
</td>
</tr>
<tr>
<td style="text-align: center;">中断</td>
<td>interrupt()</td>
<td>
<p>调用该方法，可以让任何处于阻塞（Blocking）方法调用中的线程获得退出的机会。该方法有两个效应：</p>
<ol>
<li>线程的阻塞方法（可能）会抛出InterruptedException，程序代码可以捕获此异常并退出</li>
<li>设置线程内部“已中断”标记为true，提示线程已经被中断。可以使用isInterrupted()方法来检查该标记。即使线程没有被Block，这个标记也会被设置</li>
</ol>
<p><span style="background-color: #c0c0c0;">阻塞方法包括：join、Thread.sleep()、Object.wait()、I/O的读取方法</span>等，注意I/O读取类阻塞方法不一定会在中断时抛出异常，具体看本单元格最后一段。下面是中断方法的使用样例代码：</p>
<p>基于中断标记判断（针对处于非阻塞状态的线程）：</p>
<pre class="crayon-plain-tag">Thread t = new Thread() {
    public void run()
    {
        while ( !isInterrupted() )
            ;
        System.out.println( "interrupted" );
    }
};
t.start();
TimeUnit.SECONDS.sleep( 1 );
t.interrupt();</pre>
<p>利用异常判断（针对处于阻塞状态的线程）：</p>
<pre class="crayon-plain-tag">Thread t = new Thread() {

    public void run()
    {
        try
        {
            TimeUnit.SECONDS.sleep( 3600 );
        }
        catch ( InterruptedException e )
        {
            System.out.println( "interrupted" );
        }
    }
};
t.start();
TimeUnit.SECONDS.sleep( 1 );
t.interrupt();</pre>
<p>关于interrupt()方法，还需要注意：</p>
<ol>
<li>如果线程在调用Object.wait()、join()、sleep()方法时被其它线程中断，其中断标记位会被清除，并接收到InterruptedException</li>
<li>如果线程在InterruptibleChannel上执行I/O时被其它线程中断，则通道被关闭，线程的中断标记位被设置，并接受到ClosedByInterruptException。 ServerSocketChannel、SocketChannel、FileChannel、DatagramChannel等都是可中断通道</li>
<li>如果线程在Selector上执行select操作时被中断，则线程的中断标记位被设置，并立即返回</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">竞态条件与同步</span></div>
<p>竞态条件（Race Condition），是指<span style="background-color: #c0c0c0;">计算的输出</span>依赖于<span style="background-color: #c0c0c0;">不受控制</span>的事件的出现<span style="background-color: #c0c0c0;">顺序</span>或者出现时机，具体来理解此定义中的关键词：</p>
<ol>
<li>计算的输出：比如方法调用的结果</li>
<li>不受控制：比如线程的调度是不受控制的，具有随机性</li>
<li>顺序或时机：比如哪个线程先执行，执行到哪一行代码并OS切换出去，哪个线程被切换进来获得CPU并执行</li>
</ol>
<p>下面举个浅显的例子，假设有这样一个计算器：</p>
<pre class="crayon-plain-tag">public class Calculator
{
    private final int[] input;
    private int         start;
    private int         end;
    public Calculator( int start, int end )
    {
        super();
        this.input = new int[] { start, end };
        reset();
    }
    private void reset()
    {
        start = input[0];
        end = input[1];
    }
    public int add()
    {
        int res = 0;
        for ( ; start &lt;= end; start++ )
            res += start;
        reset();
        return res;
    }
}</pre>
<p>我们期望它每次计算都能输出正确的结果，下面有两个线程连续调用计算器100次：</p>
<pre class="crayon-plain-tag">private static class CalculateThread extends Thread
{
    private Calculator calculator;

    public CalculateThread( Calculator calculator )
    {
        super();
        this.calculator = calculator;
    }

    public void run()
    {
        for ( int i = 0; i &lt; 1000; i++ )
            System.out.println( calculator.add() );
    }
}

public static void main( String[] args )
{
    Calculator calculator = new Calculator( 0, 100 );
    new CalculateThread( calculator ).start(); //线程1
    new CalculateThread( calculator ).start(); //线程2
}</pre>
<p>观察计算结果，可以看到，大部分的调用能够得到正确的和5050，但是会有一些随机的值夹杂其中，例如0、4189、1766…… 而且每次运行的输出结果都不一样。这就是典型的竞态条件场景了，线程1在调用add()方法时，随时可能因为OS的线程调度而暂停运行，这个时候calculator处于一种<span style="background-color: #c0c0c0;">不一致的中间状态</span>，此时线程2来发起/继续它的add()调用，calculator的两个状态字段的值是不确定的、随机的，因而计算结果必然不可靠。</p>
<p>之所以大部分结果是正确的，是因为CPU分配给线程的时间片相对于add()中的循环来说，是非常漫长的，因此大部分情况下线程能够不受干扰的一次执行完这个方法。</p>
<p>即使是一些非常简单的代码，也可能引入竞态条件：</p>
<pre class="crayon-plain-tag">public class IntCounterTest {
	private static int counter = 0;
	private static class CounterThread extends Thread {
		public void run() {
			for (int i = 0; i &lt; 10000000; i++)
				synchronized (IntCounterTest.class) {
					counter++;
				}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		CounterThread t1 = new CounterThread();
		t1.start();
		CounterThread t2 = new CounterThread();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(counter);
	}
}</pre>
<p>上面的例子中，我们让t1、t2两个线程分别把计数器的值增加1000万，期望结果应该是2000万，但是几乎每次运行结果都不正确。 什么原因呢？因为++操作符至少包含三条指令：</p>
<ol>
<li>读取变量的当前值到寄存器</li>
<li>将此值加一</li>
<li>将新值写回到变量</li>
</ol>
<p>如果线程t1在执行这三条指令期间受到干扰，例如：</p>
<ol>
<li>执行完第二条指令后，OS调度导致t1让出CPU，由t2执行</li>
<li>多核CPU场景下，t1执行完第二指令后，t2连续执行三条指令</li>
</ol>
<p>就必然导致数据状态的不一致。</p>
<p>出现<span style="background-color: #c0c0c0;">竞态条件的根本原因</span>是一项操作（例如一个方法调用、或者一个操作符）<span style="background-color: #c0c0c0;">不是原子的（atomic）</span>，物理学曾经认为原子是不可再分的，我们借用这一概念，用来描述一个<span style="background-color: #c0c0c0;">不可中断</span>的操作——要么不做、要么完成。</p>
<div class="blog_h3"><span class="graybg">synchronized</span></div>
<p>要让一个Java的方法变为原子的，那么就需要线程执行这个方法所有指令的整个过程不被干扰，即不会有其他线程来修改共享资源的状态。达到这一目标最直接的方法就是使用互斥锁（Mutex Lock）机制，该机制可以保证同一时刻只有一个线程对关键的、修改共享资源状态的代码段具有访问权。Java在语言级别上支持互斥锁机制——<span style="background-color: #c0c0c0;">synchronized</span>关键字，我们可以这样对上例的代码进行改写：</p>
<pre class="crayon-plain-tag">private static class CounterThread extends Thread
{
    public void run()
    {
        for ( int i = 0; i &lt; 10000000; i++ )
            //限制同一时刻只有一个人能访问对象IntCounterTest.class
            //由于这个类对象是全局唯一的，因此同一时刻只有一个线程能对counter进行递增
            synchronized ( IntCounterTest.class )
            {
                counter++;
            } //在同步块的边界，所有寄存器变量的值被视为无效
    }
}</pre>
<p>这样再执行，结果就总是2000万了。不过使用synchronized的代价是高昂的，在我机器上测试，修改前后代码运行耗时相差超过100倍。Java5提供的AtomicInteger性能相对较好（仍然有10倍性能差距），可以用来代替上例的int。</p>
<p>等待（阻塞）在synchronized块入口的线程，和sleep的线程、wait的线程，从Linux系统的角度来说，都处于S（可中断睡眠）状态。</p>
<div class="blog_h3"><span class="graybg">volatile</span></div>
<p>在Java中，除<span style="background-color: #c0c0c0;">了long、double以外</span>的变量（包括引用）的基本<span style="background-color: #c0c0c0;">加载、存储</span>是原子操作，因此这些变量的读、写操作不会存在中间状态。即便如此，没有任何保护错误的共享变量仍然是不安全的，因为Java的内存模型允许线程持有<span style="background-color: #c0c0c0;">共享变量的本地内存（Local Memory，往往是寄存器）拷贝</span>，这就意味着， 一个线程修改了某个共享变量后，其它线程可能不会立刻发现。解决这一问题的方法有两种：</p>
<ol>
<li>禁止直接访问变量，使用synchronized保护的getter/setter</li>
<li>声明变量为volatile</li>
</ol>
<p>第二种方法更加优雅，volatile<span style="background-color: #c0c0c0;">可以理解为对单个变量的读写操作进行了同步（JSR-133）</span>，volatile关键字的作用包括：</p>
<ol>
<li>确保线程每次读取变量时，都去主存读取</li>
<li>确保线程每次写入变量时，都写出到主存</li>
<li>Happens-Before原则：对volatile字段的写操作，必然发生在后续读操作之前</li>
</ol>
<p>volatile关键字可以用在<span style="background-color: #c0c0c0;">各种基本类型的变量上，甚至是数组或者对象</span>，但是对于后两者，其volatile语义是作用在引用（指针）上。合理使用volatile可以避免不必要的同步操作，从而提高应用程序性能。</p>
<div class="blog_h3"><span class="graybg">明确锁定</span></div>
<p>Java5的concurrent包引入接口：<pre class="crayon-plain-tag">java.util.concurrent.locks.Lock</pre> ，其功能与synchronized关键字类似，在代码开始处调用lock，结束处调用unlock，就可以有效的实现同步化。下面是该接口的标准用法：</p>
<pre class="crayon-plain-tag">public class MainClass
{
    private Lock   lock;

    private Object shared;

    public void operation()
    {
        lock.lock(); //获得锁，如果无法得到，线程将进入不可中断的等待状态
        try
        {
            operate( shared ); //操作共享资源
        }
        finally
        {
            lock.unlock(); //释放锁
        }
    }
}</pre>
<p>Lock接口提供了比synchronized更加细致的控制方式，它提供以下方法： </p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>lock()</td>
<td>获取锁，如果无法得到，线程被禁止调度（通过调用unsafe.park实现），直到获得锁</td>
</tr>
<tr>
<td>unlock()</td>
<td>释放锁 </td>
</tr>
<tr>
<td>trylock()</td>
<td>尝试获取锁，如果无法得到，将返回false而不是阻塞，可以提供一个返回false前等待的延时</td>
</tr>
<tr>
<td>newCondition()</td>
<td>创建一个与此锁关联的Condition对象，Condition可以代替Object.wait/notify/notifyAll</td>
</tr>
</tbody>
</table>
<p>Lock最常用的实现是：<pre class="crayon-plain-tag">ReentrantLock</pre> ，即<span style="background-color: #c0c0c0;">可重入锁</span>：如果当前线程已经持有锁L，那么调用另外一个需要该锁的方法时，当前线程<span style="background-color: #c0c0c0;">不需要释放锁而重新获取</span>，而是简单的对锁的计数++，最退出被调用方法时，则对计数--，这与synchronized关键字的语义是一致的。不是所有Lock的实现都需要支持可重入语义。ReentrantLock提供以下重要方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">方法 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>getHoldCount()</td>
<td>返回当前线程对Lock计数的数量，返回0代表当前线程没有持有锁</td>
</tr>
<tr>
<td>isLocked()</td>
<td>该锁是不是被某个线程持有 </td>
</tr>
<tr>
<td>isHeldByCurrentThread()</td>
<td>锁是否被当前线程持有</td>
</tr>
<tr>
<td>getQueueLength()</td>
<td>得到获取此锁的线程的数量</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">公平锁</span></div>
<p>所谓公平锁是指相对公平的响应锁请求的线程，避免发生线程饥饿现象（一直等待而无法得到锁）。</p>
<p><pre class="crayon-plain-tag">new ReentrantLock(true)</pre> 可以构建公平锁，它基本上以先进先出的顺序来服务锁的请求者。开发者可以实现锁类，来实现其它公平策略。</p>
<div class="blog_h3"><span class="graybg">语句重排效应</span></div>
<p>JVM可以在不影响语义的情况下，对执行<span style="background-color: #c0c0c0;">指令的顺序进行重新排列</span>。这种重排是不考虑并发效应的：</p>
<pre class="crayon-plain-tag">private int i;
private int j;
public void set()
{
    //下面两个语句的顺序JVM可以重排
    i = 0;
    j = 1; //该语句可以重排到该方法的任何地方
    if ( i == 0 ) //i = 0的指令必须在这一句前面
        i = 1;
}</pre>
<p>这种指令重排机制可能导致多线程程序的运行结果和预期不符，因此<span style="background-color: #c0c0c0;">不要尝试依靠代码顺序来避免同步</span>，典型的反例是双重检查惯例。synchronized机制可以有效的避免指令重排效应。</p>
<p>在<span style="background-color: #c0c0c0;">JSR-133</span>中， volatile的语义被增强，<span style="background-color: #c0c0c0;">禁止了volatile变量与普通变量之间重排序</span>。因此Java5之后，volatile与synchronized具有类似的语义——针对单个变量读写的锁。</p>
<div class="blog_h3"><span class="graybg">Atomic类</span></div>
<p>Java5的concurrent包引入了若干Atomic类，这些类支持以原子的方式进行操作，可以避免同步、提高性能。</p>
<p>Atomic家族在内部实现上使用了一种乐观同步机制：当前线程假设没有其它线程修改共享变量，并计算变量的新值，在尝试更新变量时检查是否有其它线程对变量进行了修改，如果被修改，则使用最新的值重新执行前面的步骤。</p>
<div class="blog_h2"><span class="graybg">死锁</span></div>
<p>不管是Java还是数据库，死锁的发生都有一个前提，那就是<span style="background-color: #c0c0c0;">消费者在独占（不释放）某些资源的同时，又请求其它资源的独占</span>，消费者在Java中就是线程，资源就是对象监视器或者锁。</p>
<p>一个经典的死锁例子是“哲学家进餐问题”，该问题的描述如下：</p>
<ol>
<li>N位哲学家坐在圆桌上，它们只做两件事情，思考和吃饭</li>
<li>圆桌中间放着一大盘米饭，每位哲学家左右两边各放一只筷子，共计N只筷子</li>
<li>哲学家必须先拿起左边的筷子，然后再拿起右边的筷子，才能把米饭盛到自己的碗里</li>
</ol>
<p>由于哲学家们从不交流，就可能导致吃不上饭的问题：每位哲学家都拿着左手的筷子，并等着右手的筷子。</p>
<p>在这个场景中，筷子就是共享资源，每位哲学家都会尝试在得到一个资源的同同时，请求另外一个资源。致命的是，这些资源<span style="background-color: #c0c0c0;">请求形成了一个环路</span>，导致任何一个资源都永远无法释放。</p>
<p>下面是哲学家问题的Java代码：</p>
<pre class="crayon-plain-tag">import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.RandomUtils;

public class PhilosopherProblem
{
    private static void printf( String fmt, Object... args )
    {
        System.out.println( Thread.currentThread().getName() + " " + String.format( fmt, args ) );
    }

    private static class Chopstick extends ReentrantLock
    {
        private static final long serialVersionUID = 1L;

        private int               id;

        public Chopstick( int id )
        {
            super();
            this.id = id;
        }

        @Override
        public void lock()
        {
            super.lock();
            printf( "got chopstick %s", id );
        }

        @Override
        public void unlock()
        {
            super.unlock();
            printf( "released chopstick %s", id );
        }
    }

    private static class Philosopher extends Thread
    {
        private Chopstick left;

        private Chopstick right;

        public Philosopher( String name, Chopstick left, Chopstick right )
        {
            super();
            this.left = left;
            this.right = right;
            setName( name );
        }

        @Override
        public void run()
        {
            for ( ;; )
            {
                try
                {
                    meditate();
                    eat();
                }
                catch ( InterruptedException e )
                {
                    e.printStackTrace();
                }
            }
        }

        public void meditate() throws InterruptedException
        {
            int ms = RandomUtils.nextInt( 1000, 10000 );
            TimeUnit.MILLISECONDS.sleep( ms );
            printf( "meditated for %d ms", ms );
        }

        public void eat() throws InterruptedException
        {
            try
            {
                left.lock();
                try
                {
                    right.lock();
                    int ms = RandomUtils.nextInt( 1000, 10000 );
                    TimeUnit.MILLISECONDS.sleep( ms );
                    printf( "ate for %d ms", ms );
                }
                finally
                {
                    right.unlock();
                }
            }
            finally
            {
                left.lock();
            }
        }
    }

    public static void main( String[] args )
    {
        Chopstick cs0 = new Chopstick( 0 );
        Chopstick cs1 = new Chopstick( 1 );
        Chopstick cs2 = new Chopstick( 2 );
        Chopstick cs3 = new Chopstick( 3 );
        Chopstick cs4 = new Chopstick( 4 );
        new Philosopher( "Socrates", cs0, cs1 ).start();
        new Philosopher( "Plato", cs1, cs2 ).start();
        new Philosopher( "Aristotle", cs2, cs3 ).start();
        new Philosopher( "Thales", cs3, cs4 ).start();
        new Philosopher( "Pythagoras", cs4, cs0 ).start();
    }
}</pre>
<p>程序运行起来后，很快即陷入死锁，不再有任何输出。 很多工具（TPTP、JProfiler、JMC）可以协助识别死锁，帮助改进程序。例如JMC检测上述程序死锁的输出如下：</p>
<p><img class="aligncenter  wp-image-8132" src="https://blog.gmem.cc/wp-content/uploads/2008/11/deadlock.png" alt="deadlock" width="671" height="281" /></p>
<div class="blog_h3"><span class="graybg">避免死锁的一般经验</span></div>
<ol>
<li>当持有一个锁的时候，线程应当避免再去获得其它锁</li>
<li>确保相关的锁总是以一致的顺序被获得</li>
<li>总是在<pre class="crayon-plain-tag">finally</pre> 块中调用<pre class="crayon-plain-tag">unlock()</pre> </li>
<li>使用时限锁：<pre class="crayon-plain-tag">tryLock()</pre> ，在指定时间内没有获得锁则退出，而不是一直等待</li>
</ol>
<div class="blog_h2"><span class="graybg">等待与通知</span></div>
<p>每个Java对象都有一个伴随的<span style="background-color: #c0c0c0;">监视器（Monitor）对象</span>，使用监视器可以实现锁定。同样的每个对象也有一种机制让它称为<span style="background-color: #c0c0c0;">等待区</span>，这种机制的想法其实很单纯：</p>
<ol>
<li>某个线程等待一个特定条件的出现</li>
<li>其它线程可以为它创建这个条件</li>
<li>当其它线程创建条件时，会通知正在等待该条件的线程</li>
</ol>
<p>该<span style="background-color: #c0c0c0;">等待-通知机制</span>是通过定义在<pre class="crayon-plain-tag">java.lang.Object</pre> 中的方法来实现的：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>wait()</td>
<td>
<p>当前线程调用object.wait()等待条件的发生，当前线程必须持有该对象的监视器，例如处于<pre class="crayon-plain-tag">synchronized(object){}</pre> 块中，调用该方法导致当前线程放弃监视器（锁定）</p>
<p>可以指定一个可选的timeout，在超时后，该调用自动返回</p>
<p>该方法返回时，线程必须获取监视器</p>
</td>
</tr>
<tr>
<td>notify() </td>
<td>通常正在等待的某个线程，事件已经发生，当前线程必须持有该对象的监视器</td>
</tr>
<tr>
<td>notifyAll()</td>
<td>通知所有正在等待的线程</td>
</tr>
</tbody>
</table>
<p>表格里面已经提到，等待通知机制中的方法都需要持有目标对象的监视器，这是因为该机制本身存在竞态条件问题，因而必须结合锁一起使用。等待-通知机制的竞态条件场景举例如下：</p>
<ol>
<li>Thread1测试条件（通常是一个共享的变量），确认它需要等待</li>
<li>Thread2设置条件</li>
<li>Thread2调用notify()，试图唤醒Thread1，但是这不会成功，因为Thread1尚未等待</li>
<li>Thread1调用wait()方法进入等待</li>
</ol>
<p>等待通知机制<span style="background-color: #c0c0c0;">不会在调用wait()与释放锁之间、返回wait()与重新获得锁之间发生竞态条件</span>。</p>
<p>对于<span style="background-color: #c0c0c0;">wait()的调用一般都位于循环结构</span>中，另外需要注意：</p>
<ol>
<li>线程应该总是在持有锁时、在wait()之前测试条件不满足，然后调用wait()等待</li>
<li>线程应该总是从wait()返回后，判断条件是否满足，还是需要继续等待。因为可能有其它线程也被唤醒，并且它改变了条件（即使被唤醒，也不代表条件满足）</li>
</ol>
<p>下面是一个简单的例子：</p>
<pre class="crayon-plain-tag">public class WaitAndNotifyMechanism
{
    private static boolean condition;
    private static Object lock = new Object();
    private static class Consumer extends Thread
    {
        @Override
        public void run()
        {
            for ( ;; )
            {
                try
                {
                    //这里是一个全局锁，因此锁定哪个对象不重要，只要对象是全局唯一的
                    //甚至可以把WaitAndNotifyMechanism.lock作为锁
                    synchronized ( WaitAndNotifyMechanism.class )
                    {
                        //检测条件时必须锁定
                        if ( condition )
                        {
                            condition = false; //消费
                            System.out.println( currentThread().getName() );
                        }
                        WaitAndNotifyMechanism.class.wait(); //但是必须调用作为锁的对象的wait()方法
                    }
                }
                catch ( InterruptedException e )
                {
                }
            }
        }
    }

    public static void main( String[] args ) throws InterruptedException
    {
        //两个消费者
        new Consumer().start();
        new Consumer().start();
        //主线程扮演的生产者
        for ( ;; )
        {
            TimeUnit.SECONDS.sleep( RandomUtils.nextInt( 1, 6 ) );
            //修改条件时必须锁定
            synchronized ( WaitAndNotifyMechanism.class )
            {
                if ( !condition )
                {
                    condition = true; //生产
                    WaitAndNotifyMechanism.class.notify(); //唤醒一个等待的线程
                }
            }
        }
    }
}</pre>
<div class="blog_h3"><span class="graybg">条件变量（Condition Variable）</span></div>
<p>条件变量是有某个锁关联的变量，它是很多线程系统使用的同步模型，它与Java的等待-唤醒机制很类似。POSIX条件变量的四个基本函数——wait()、timed_wait()、signal()、broadcast()与Java中Object的方法一一对应，它们在逻辑上处理上也是一致的。条件变量的wait()、signal()操作需要当前线程持有互斥锁， 从wait()调用返回时需要重新得到互斥锁。</p>
<p>但是Java的等待-唤醒机制更加易用，它相当于把<span style="background-color: #c0c0c0;">条件变量与互斥锁整合</span>在了一起，因而在synchronized代码块中调用被同步对象的wait()、notify()方法是很自然的操作方式。</p>
<p>Java5引入的Lock接口，支持创建条件变量，<span style="background-color: #c0c0c0;">Lock与Condition是绑定的</span>，就像既有的等待-唤醒机制一样。这两个接口提供的灵活性就像其它线程系统的条件变量一样。下面是一个来自JDK的例子，我们可以看一下条件变量的用法：</p>
<pre class="crayon-plain-tag">/**
 * 一个有界的缓冲区
 * @param &lt;T&gt; 缓冲区元素类型
 */
class BoundedBuffer&lt;T&gt;
{
    //互斥锁对象
    private final Lock      lock     = new ReentrantLock();

    //从互斥锁创建两个条件变量，条件变量和锁是绑定的
    private final Condition notFull  = lock.newCondition(); //消费者通知生产者用

    private final Condition notEmpty = lock.newCondition(); //生产者通知消费者用

    private int             putptr, takeptr, count;

    private T[]             items;

    @SuppressWarnings ( "unchecked" )
    public BoundedBuffer( int capacity )
    {
        items = (T[]) new Object[capacity];
    }

    //由消费者线程调用
    public void put( T x ) throws InterruptedException
    {
        //修改共享数据，因此首先需要互斥锁定
        lock.lock();
        try
        {
            while ( count == items.length )
                //如果缓冲区目前是满的，放弃锁定并等待
                //与既有的等待-唤醒机制一样，在唤醒后，需要重新获得锁
                notFull.await();
            items[putptr] = x;
            if ( ++putptr == items.length ) putptr = 0;
            ++count;
            //唤醒正在等待缓冲非空信号的消费者线程
            notEmpty.signal();
        }
        finally
        {
            lock.unlock(); //总是解除锁定
        }
    }

    //由生产者线程调用
    public Object take() throws InterruptedException
    {
        lock.lock();
        try
        {
            while ( count == 0 )
                notEmpty.await(); //如果当前缓冲区为空，等待
            T x = items[takeptr];
            if ( ++takeptr == items.length ) takeptr = 0;
            --count;
            notFull.signal(); //唤醒等待缓冲区非满的生产者线程
            return x;
        }
        finally
        {
            lock.unlock();
        }
    }
}</pre>
<p>条件变量比起既有的等待-唤醒机制，其优势在于：</p>
<ol>
<li>使用Lock时，Condition是必要的，因为Lock的wait/notify方法已经被覆盖，用来实现Lock对象的功能</li>
<li> 每个Lock可以创建多个对应的Condition，通过给Condition变量进行合理命名，可以使代码的语义更加直白</li>
</ol>
<div class="blog_h2"><span class="graybg">线程本地变量</span></div>
<p>从JDK1.2开始，ThreadLocal类被引入。该类用于存放线程本地（Local，局部）变量。这种变量与普通的共享变量不同，它为每个线程设置不同的副本，不同线程之间互不干扰。</p>
<p>ThreadLocal往往添加<pre class="crayon-plain-tag">private static</pre> 限定符，并通过<pre class="crayon-plain-tag">static getter()</pre> 暴露访问：</p>
<pre class="crayon-plain-tag">class SharedObject{
    //普通变量，会被多个线程共享，存在并发问题
    private static Map&lt;String,String&gt; normalVariables; 
    //线程本地变量，每个线程都有自己的副本，不存在并发问题
    private static ThreadLocal&lt;Map&lt;String,String&gt;&gt; variables = new ThreadLocal&lt;Map&lt;String,String&gt;&gt;(){
        //可以覆盖此方法，为每个线程的副本提供初值
        protected Map&lt;String,String&gt; initialValue() {
            return new HashMap&lt;String,String&gt;();
        };
    };

    public static ThreadLocal&lt;Map&lt;String,String&gt;&gt; getVariables()
    {
        return variables;
    }
}</pre>
<p>调用ThreadLocal的set/get方法，可以设置、读取本地变量的值，在JDK1.5之后，ThreadLocal类提供了泛型支持。</p>
<p>线程本地变量常被用来<span style="background-color: #c0c0c0;">避免方法调用的参数传递</span>，理论上将，可以使用线程本地变量完全取代方法参数，而保持等同的应用逻辑。</p>
<p>除非存在外部引用，否则线程本地变量将在线程终结时自动回收。</p>
<div class="blog_h2"><span class="graybg">高级同步类</span></div>
<div class="blog_h3"><span class="graybg">Semaphore</span></div>
<p>在Java中，信号量基本上可以认为是带有计数器的Lock。计数器许可只有1的信号量和Lock是一回事，除了某些Lock的实现支持重入。下面的代码是一个通用的固定大小对象池，演示了信号量的用法：</p>
<pre class="crayon-plain-tag">package cc.gmem.study.j5ia.concurrent.adv;

import java.util.List;
import java.util.concurrent.Semaphore;

public class CommonPool&lt;T&gt;
{
    private int         maxPoolSize;

    private Semaphore   available;

    protected T[]       items;

    protected boolean[] used;

    @SuppressWarnings ( "unchecked" )
    public CommonPool( List&lt;? extends T&gt; objects )
    {
        //对象池的最大尺寸
        this.maxPoolSize = objects.size();
        //对象池内存存储结构数组
        this.items = (T[]) objects.toArray();
        //构建一个许可数目与对象池大小相同的公平信号量
        this.available = new Semaphore( maxPoolSize, true );
        this.used = new boolean[maxPoolSize];
    }

    /**
     * 从池中借出一个对象
     */
    public T borrowItem() throws InterruptedException
    {
        //首先，当前线程需要获取一个许可，如果没有需求，则陷入等待
        available.acquire();
        return getNextAvailableItem();
    }

    /**
     * 归还一个对象到池中
     */
    public void returnItem( T item )
    {
        if ( markAsUnused( item ) ) //只有是池里既有的对象才能放回去
            //如果返回成功，则释放一个许可
            available.release();
    }

    /**
     * 获取下一个可用对象
     */
    protected synchronized T getNextAvailableItem()
    {
        for ( int i = 0; i &lt; maxPoolSize; ++i )
        {
            if ( !used[i] )
            {
                used[i] = true;
                return items[i];
            }
        }
        return null;
    }

    /**
     * 标记某个对象为可用
     */
    protected synchronized boolean markAsUnused( T item )
    {
        for ( int i = 0; i &lt; maxPoolSize; ++i )
        {
            if ( item == items[i] )
            {
                if ( used[i] )
                {
                    used[i] = false;
                    return true;
                }
                else return false;
            }
        }
        return false;
    }
}</pre>
<p>对象池的测试代码：</p>
<pre class="crayon-plain-tag">List&lt;Object&gt; objects = new ArrayList&lt;Object&gt;();
objects.add( new Object() );
objects.add( new Object() );
objects.add( new Object() );
final CommonPool&lt;Object&gt; pool = new CommonPool&lt;Object&gt;( objects );
//把三个对象都借走
Object[] borrowed = new Object[] { pool.borrowItem(), pool.borrowItem(), pool.borrowItem() };
new Thread() {
    public void run()
    {
        try
        {
            //阻塞，直到3秒后
            pool.borrowItem();
            System.out.println( "Borrowed an object." );
            //阻塞，直到3秒后
            pool.borrowItem();
            System.out.println( "Borrowed another object." );
        }
        catch ( InterruptedException e )
        {
        }
    }
}.start();

for ( Object item : borrowed )
{
    TimeUnit.SECONDS.sleep( 3 );
    pool.returnItem( item );
}</pre>
<div class="blog_h3"><span class="graybg">CyclicBarrier</span></div>
<p>屏障类很简单，允许多个线程在一个屏障点集中，汇总结果、并继续下一步工作。下面我们以公司组织去水长城旅游的例子来说明该类的用法：</p>
<pre class="crayon-plain-tag">import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.time.DateFormatUtils;

public class GreatWaterWallTourism
{
    /**
     * 小院屏障：所有车到小院集合，换乘旅游大巴
     */
    private static CyclicBarrier farmyardBarrier;

    /**
     * 去往水长城的旅游大巴
     */
    private static TouristBus    touristBus;

    private static class TouristBus extends Thread
    {
        private List&lt;String&gt; persons = new ArrayList&lt;String&gt;();

        public void getOn( List&lt;String&gt; persons )
        {
            this.persons.addAll( persons );
        }

        @Override
        public void run()
        {
            System.out.println( persons + " are leaving for the great water wall" );
        }
    }

    /**
     * 去往小院的交通工具
     */
    private abstract static class Vehicle extends Thread
    {

        protected List&lt;String&gt; persons = new ArrayList&lt;String&gt;();

        @Override
        public void run()
        {

            try
            {
                doRun();
                farmyardBarrier.await(); //等待其它车辆抵达
                touristBus.getOn( persons ); //把人装进旅游大巴
            }
            catch ( Exception e )
            {
            }

        }

        protected String now()
        {
            return DateFormatUtils.format( Calendar.getInstance(), "HH:mm:ss" );
        }

        protected abstract void doRun() throws InterruptedException;
    }

    /**
     * 小汽车
     */
    private static class Car extends Vehicle
    {
        protected void doRun() throws InterruptedException
        {
            persons.add( "Fu Chang" );
            persons.add( "Bing Lee" );
            System.out.println( persons + " are going to the farmyard by car" );
            TimeUnit.SECONDS.sleep( 1 );
            System.out.println( "Car arrived at " + now() );
        }
    }

    /**
     * 十一路
     */
    private tatic class BusNo11 extends Vehicle
    {

        @Override
        protected void doRun() throws InterruptedException
        {
            persons.add( "Zhen Wong" );
            System.out.println( persons + " are taking bus number 11 to the farmyard" );
            TimeUnit.SECONDS.sleep( 10 );
            System.out.println( "Bus number 11 arrived at " + now() );
        }

    }

    public static void main( String[] args ) throws Exception
    {
        touristBus = new TouristBus();
        farmyardBarrier = new CyclicBarrier( 3 );

        new Car().start();
        new BusNo11().start();
        farmyardBarrier.await();

        System.out.println( "Tourist bus is about to depart after 3s" );
        TimeUnit.SECONDS.sleep( 3 );
        touristBus.start();
    }
}</pre>
<p>输出内容如下：</p>
<pre class="crayon-plain-tag">[Fu Chang, Bing Lee] are going to the farmyard by car
[Zhen Wong] are taking bus number 11 to the farmyard
Car arrived at 14:18:52
Bus number 11 arrived at 14:19:01
Tourist bus is about to depart after 3s
[Zhen Wong, Fu Chang, Bing Lee] are leaving for the great water wall</pre>
<div class="blog_h3"><span class="graybg">CountdownLatch</span></div>
<p>倒数计数器，这个类和屏障类很相似，它允许一个或者多个线程等待计数变为零，任何线程都可以将计数器的值倒数：</p>
<pre class="crayon-plain-tag">//计数为3的倒数门栓
final CountDownLatch latch = new CountDownLatch( 3 );
latch.countDown(); //减少计数
new Thread() {
    public void run()
    {
        try
        {
            TimeUnit.SECONDS.sleep( 3 );
        }
        catch ( InterruptedException e )
        {
        }
        latch.countDown();//减少计数
        latch.countDown();//减少计数
    };
}.start();
latch.await();//等待计数为0</pre>
<div class="blog_h3"><span class="graybg">Exchanger</span></div>
<p>定义一个交换点（同步点），两个线程会在此点交换数据，先到达的线程会等待，直到配对的线程到达。下面举一个早教中心运动课的例子，参与的孩子们相互传球玩：</p>
<pre class="crayon-plain-tag">public class BallExchangeGame
{
    private static Exchanger&lt;Ball&gt; ballExchanger = new Exchanger&lt;Ball&gt;();

    private static class Ball
    {
        private String color;

        public Ball( String color )
        {
            super();
            this.color = color;
        }

        public String toString()
        {
            return color + " ball";
        }
    }

    private static class Child extends Thread
    {
        private Ball ownedBall;

        public Child( String name, Ball ownedBall )
        {
            super();
            setName( name );
            this.ownedBall = ownedBall;
        }

        @Override
        public void run()
        {
            for ( ;; )
            {
                try
                {
                    TimeUnit.SECONDS.sleep( RandomUtils.nextInt( 1, 10 ) );
                    ownedBall = ballExchanger.exchange( ownedBall );
                    System.out.println( getName() + " got " + ownedBall );
                }
                catch ( InterruptedException e )
                {
                }
            }
        }
    }

    public static void main( String[] args ) throws InterruptedException
    {
        new Child( "Cai Cai", new Ball( "Colorized" ) ).start();
        new Child( "Yu Han", new Ball( "Red" ) ).start();
        new Child( "Tiao Tiao", new Ball( "Black" ) ).start();
    }
}</pre>
<p> 观察该程序输出时，会发现控制台总是成对的打印41行的内容，这是因为“交换”是相互的，先提出交换的孩子必须等待另外一个孩子也愿意交换。</p>
<div class="blog_h3"><span class="graybg">ReadWriteLock</span></div>
<p>JDK 1.5包含读写锁接口ReadWriteLock，它简单的包含两个方法，分别用于获得读锁（<pre class="crayon-plain-tag">Lock readLock()</pre> ）、写锁（<pre class="crayon-plain-tag">Lock writeLock()</pre> ）。读锁是共享锁，允许多个线程持有；写锁是独占锁，只能被一个线程持有。此外，一旦有线程持有读锁，就不能有其它线程获取写锁。</p>
<p>JDK自带的读写锁实现类是一个可重入的锁：<pre class="crayon-plain-tag">ReentrantReadWriteLock</pre> </p>
<div class="blog_h2"><span class="graybg">JVM线程调度</span></div>
<div class="blog_h3"><span class="graybg">线程状态</span></div>
<p>从概念上讲，每个JVM线程在整个生命周期中，必然处于下列状态之一：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;"> 线程状态</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Initial</td>
<td>初始状态， 从线程对象被构造，一直到start()方法被调用，处于这一阶段</td>
</tr>
<tr>
<td>Runnable</td>
<td>
<p>可运行状态，线程的start()方法被调用后，就会处于该状态，JDK提供了多种方式可以使线程离开Runnable状态</p>
<p>处于该状态的进程不一定正在占用CPU资源</p>
<p>某些情况下，把Runnable状态进行以下细分：</p>
<ol>
<li>Runnable：可以运行，但是目前没有占用CPU</li>
<li>Running：正在占用CPU</li>
</ol>
</td>
</tr>
<tr>
<td>Blocked</td>
<td>
<p>阻塞状态，处于该状态的线程不能运行，因为它在等待某种事件（定时器、I/O等）的发生，下列情况下线程会进入阻塞状态：</p>
<ol>
<li>等待I/O，例如读取Socket时</li>
<li>尝试进入正被其它线程占据的临界区域（Synchronized）受阻</li>
<li>尝试获取其它占据的Lock时受阻</li>
<li>执行Thread或者Object提供的阻塞性方法，例如sleep()、wait()、join()等</li>
</ol>
<p>某些情况下，把Blocked状态进行以下细分：</p>
<ol>
<li>Blocked：等待I/O、进入临界区域受阻时进入此状态</li>
<li>Waiting：调用Object.wait()后进入此状态</li>
<li>Sleeping：调用Thread.sleep()后进入此状态</li>
</ol>
</td>
</tr>
<tr>
<td>Exiting</td>
<td>一旦线程从run()方法返回，或者被调用stop()方法，就进入该状态，某些情况下也称为Dead状态</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Java线程的优先级</span></div>
<p>Java线程调度是基于优先级进行的，但是最终还是受制于具体的操作系统，大部分基于抢占式调度的OS能够很好的支持这种调度方式。</p>
<p>Java中定义了11个优先级，其中开发者可以用到其中的十个（从<pre class="crayon-plain-tag">Thread.MIN_PRIORITY</pre> 到<pre class="crayon-plain-tag">Thread.MAX_PRIORITY</pre> ）。</p>
<p>需要注意的是，并不是优先级低的线程总是要等待优先级低的线程让出CPU，为了防止线程饥饿，OS会进行较为复杂的公式计算，来决定到底哪个线程被调度执行。</p>
<p>JDK提供的一些方法可以显式的影响线程调度，例如suspend()可以让当前线程保持在Blocked状态，resume()则让其恢复，但是这两个方法已经被废弃了。</p>
<p><pre class="crayon-plain-tag">Thread.yield()</pre> 会请求OS选择其它线程去运行，该方法的真实效果依赖于OS，大部分时间没有任何意义。在协程（Green Thread）模式下，该方法很有用，但是现代JVM早已抛弃协程，而使用OS级别的线程。</p>
<div class="blog_h3"><span class="graybg">Java线程的不同实现方式</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 实现方式</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Green Thread</td>
<td>
<p>也称<span style="background-color: #c0c0c0;">协程</span>，这种实现方式是最简单的，OS不知道任何Java线程的存在，在它看来JVM就是单线程的进程。</p>
<p>在这种模式下，每个线程必须保存所有信息在Thread对象中，例如线程的Stack、只是正在执行的代码的Program Counter，而JVM需要负责线程上下文的切换。</p>
<p><span style="background-color: #c0c0c0;">Green Thread</span>在其它环境下，还常常被称为<span style="background-color: #c0c0c0;">用户级别的线程</span>（User-level Thread），因为它只存在于应用程序的用户级，与OS内核无关。</p>
</td>
</tr>
<tr>
<td>Win32固有线程</td>
<td>使用Win32固有线程来实现Java线程时，Java线程和OS线程具有一对一的关系，OS对Java线程具有完全的管辖权。Windows定义的7种优先级和Java优先级具有重叠映射关系</td>
</tr>
<tr>
<td>Linux固有线程</td>
<td>直到JDK1.3，Linux上的JVM都在尝试使用Green Thread，这是因为当时的Linux内核对多线程应用的优化不好。新的Linux内核使用NPTL，JVM也使用了类似其它OS上的1:1线程映射</td>
</tr>
</tbody>
</table>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/java-threads-and-concurrent">Java线程与并发编程</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/java-threads-and-concurrent/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Java5新特性</title>
		<link>https://blog.gmem.cc/java5-new-features</link>
		<comments>https://blog.gmem.cc/java5-new-features#comments</comments>
		<pubDate>Fri, 27 Apr 2007 02:22:24 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[并发编程]]></category>
		<category><![CDATA[新特性]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=8026</guid>
		<description><![CDATA[<p>泛型（Generics） 简介 泛型，也称为参数化类型，是很多语言支持的编程范式——在定义类型、接口或者方法时将类型作为参数。Java诞生以来最重大的语法改变当属泛型，它是Java5最重要的特性。Java泛型与C++的模板机制在形式上有些类似，但是Java泛型的实现方式——类型擦除导致二者有着根本性的不同。 使用泛型，可以获得以下好处： 在编译期获得更好的类型检查 消除不必要的强制转型代码 允许编程人员实现泛型算法 使用泛型机制设计的类，最常见的是容器类，Java5的集合框架大量使用了泛型机制（类似于C++的STL库）： [crayon-69e5e344175b7503506367/] 但是，任何字段、方法参数、方法返回值需要参数化的类型，都可以从泛型机制获益，例如下面这个通用的回调接口：  [crayon-69e5e344175bb422809458/] 类/接口的泛型化 所谓泛型类型（ generic type），是指通过类型参数化的类/接口，这种类型的声明语法为： [crayon-69e5e344175bd566783598/] 类型形参可以在类定义的任何地方引用。类型参数只是一个占位符，它可以表示任意一种非基本（non-primitive ）类型。根据惯例，类型形参使用单个大写字母表示，一般E表示“元素”、K表示“键”、N表示“数字”、T表示“类型”、V表示“值”，等等。 要在代码中使用一个泛型类型，必须进行泛型类型调用（generic type invocation），此时必须把类型形参（type parameter）替换为类型实参（ <a class="read-more" href="https://blog.gmem.cc/java5-new-features">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/java5-new-features">Java5新特性</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">泛型（Generics）</span></div>
<div class="blog_h3"><span class="graybg">简介</span></div>
<p>泛型，也称为参数化类型，是很多语言支持的编程范式——在定义类型、接口或者方法时将类型作为参数。Java诞生以来最重大的语法改变当属泛型，它是Java5最重要的特性。Java泛型与C++的模板机制在形式上有些类似，但是Java泛型的实现方式——类型擦除导致二者有着根本性的不同。</p>
<p>使用泛型，可以获得以下好处：</p>
<ol>
<li>在编译期获得更好的类型检查</li>
<li>消除不必要的强制转型代码</li>
<li>允许编程人员实现泛型算法</li>
</ol>
<p>使用泛型机制设计的类，最常见的是容器类，Java5的集合框架大量使用了泛型机制（类似于C++的STL库）：</p>
<pre class="crayon-plain-tag">public interface Collection&lt;E&gt;{};
public interface Set&lt;E&gt;{};
public interface List&lt;E&gt;{};
public class ArrayList&lt;E&gt; extends AbstractList&lt;E&gt; implements List&lt;E&gt;, RandomAccess, Cloneable, java.io.Serializable{};
public interface Map&lt;K,V&gt; {}</pre>
<p>但是，任何字段、方法参数、方法返回值需要参数化的类型，都可以从泛型机制获益，例如下面这个通用的回调接口： </p>
<pre class="crayon-plain-tag">interface Callback&lt;P,R&gt;{
    R invoke(P p);
}</pre>
<div class="blog_h3"><span class="graybg">类/接口的泛型化</span></div>
<p>所谓泛型类型（ generic type），是指通过类型参数化的类/接口，这种类型的声明语法为：</p>
<pre class="crayon-plain-tag">class ClassName&lt;T1,...Tn&gt;{
    // T1...Tn是将ClassName参数化的类型形参（type parameter），或者叫类型变量（type variables），可以有1-N个
}
// 示例：
class Container&lt;I&gt; {
    private I item;
    void put(I i) {
        item = i;
    }
    I get() {
        return item;
    }
}</pre>
<p>类型形参可以<span style="background-color: #c0c0c0;">在<strong><span style="background-color: #ccffcc;">类定义的</span></strong>任何地方引用</span>。类型参数只是一个占位符，它可以表示<span style="background-color: #c0c0c0;">任意一种非基本（non-primitive ）类型</span>。根据惯例，类型形参使用单个大写字母表示，一般E表示“元素”、K表示“键”、N表示“数字”、T表示“类型”、V表示“值”，等等。</p>
<p>要在代码中使用一个泛型类型，必须进行泛型类型调用（generic type invocation），此时必须把类型形参（type parameter）替换为类型实参（ type argument），后者是真实的Java类型：</p>
<pre class="crayon-plain-tag">Container&lt;Number&gt; cn = new Container&lt;Number&gt;();
// Java 7引入了钻石操作符，可以根据cn的变量类型推倒构造器的type argument：
Container&lt;Number&gt; cn = new Container&lt;&gt;();</pre>
<p>作为类型实参的Java类型，其本身也可以是泛型类型：</p>
<pre class="crayon-plain-tag">// 不管嵌套多少层，泛型类型的类型参数都不能是占位符
Container&lt;List&lt;String&gt;&gt; cn = new Container&lt;&gt;();
// 下面的语句非法，E不是可解析的真实类型
Container&lt;List&lt;E&gt;&gt; cn = new Container&lt;&gt;();</pre>
<div class="blog_h3"><span class="graybg">原始类型（Raw Types）</span></div>
<p>所谓原始类型，是指在不指定类型实参的情况下使用泛型，例如：<pre class="crayon-plain-tag">ArrayList list = new ArrayList()</pre> 。原始类型为依赖的Java库提供的向后兼容。</p>
<p>混合使用泛型、原始类型，会在编译时得到未检查错误信息（Unchecked Error Messages），所谓Unchecked表示编译器无法得到足够的信息来完成类型推断以确保安全性。要禁止此类错误信息，可以使用编译选项<pre class="crayon-plain-tag">-Xlint:-unchecked</pre> 或者在代码上添加注解<pre class="crayon-plain-tag">@SuppressWarnings("unchecked")</pre> </p>
<div class="blog_h3"><span class="graybg">方法的泛型化</span></div>
<p>不但类可以设计为泛型，方法也可以。方法可以使用类声明的泛型形参，亦可<span style="background-color: #c0c0c0;">自己声明泛型参数</span>，声明自己的泛型参数的方法称为泛型方法（Generic method），例如：</p>
<pre class="crayon-plain-tag">public class Math {
    // 泛型方法，泛型形参列表必须在返回值之前声明：
    public static &lt;RET, ARG0, ARG1&gt; RET plus( ARG0 arg0, ARG1 arg1 ) {
        return (RET) null;
    }
}</pre>
<p>调用泛型方法的完整语法如下：</p>
<pre class="crayon-plain-tag">Integer result = Math.&lt;Integer, Double, Double&gt;plus( 1.1, 1.2 );</pre>
<p>指定泛型实参列表 不是必须的，通常编译器能够根据上下文推断。编译器的这种能力称为type inference。</p>
<div class="blog_h3"><span class="graybg">受限（Bounded）泛型形参</span></div>
<p>Java泛型支持<span style="background-color: #c0c0c0;">限制可作为泛型实参的类型的范围</span>。例如上面的plus方法，可能仅仅支持数字类型。要声明受限泛型形参，需要使用extends关键字：</p>
<pre class="crayon-plain-tag">public static &lt;RET, ARG0 extends Number, ARG1 extends Number&gt; RET plus( ARG0 arg0, ARG1 arg1 ) {
    return (RET) null;
}</pre>
<p>使用受限形参，方法体可以对形参的类型进行假设，这样就可以执行有意义的方法调用了：</p>
<pre class="crayon-plain-tag">public static &lt;T extends Comparable&lt;T&gt;&gt; int countGreaterThan( T[] anArray, T elem ) {
    int count = 0;
    for ( T e : anArray )
        // 调用比较方法，因为T必须是Comparable的子类型
        if ( e.compareTo( elem ) &gt; 0 )
            ++count;
    return count;
} </pre>
<div class="blog_h3"><span class="graybg">多重限制（Multiple Bounds） </span></div>
<p>可以在限制泛型实参必须是某个类型或者其子类型的同时，限制它必须实现某些接口：</p>
<pre class="crayon-plain-tag">// 多个接口用 &amp; 符号分隔
public static &lt;T extends Entity &amp; Serializable&gt; void save(T t){}</pre>
<div class="blog_h3"><span class="graybg">泛型通配符（Wildcard）</span></div>
<p>可以在<span style="background-color: #c0c0c0;">字段、方法形参、变量</span>的声明中使用<span style="background-color: #c0c0c0;">（不受限的）泛型通配符</span>，使用<pre class="crayon-plain-tag">?</pre> 代替实际类型即为泛型通配符，表示<span style="background-color: #c0c0c0;">泛型实参的类型未知</span>。使用通配符后：</p>
<ol>
<li>泛型类中返回参数类型的方法，类型自动变为Object</li>
<li>泛型类中需要参数类型作为入参的方法，将不能接受任何对象</li>
</ol>
<p>例如泛型类：</p>
<pre class="crayon-plain-tag">public class Value&lt;T&gt; {
    private T t;
    public Value( T t ){
        this.t = t;
    }
    public void set( T t ){
        this.t = t;
    }
    public T get(){
        return t;
    }
}</pre>
<p>如果声明参数类型为通配符：</p>
<pre class="crayon-plain-tag">Value&lt;?&gt; value = new Value&lt;Integer&gt;( 1 );
Object o = value.get(); //可以通过编译，你不知道是什么类型，所以我们给你一个根类型
value.set( 1 ); //无法通过编译，不是我们不限制类型，而是你不知道是什么类型</pre>
<p>就意味着，你<span style="background-color: #c0c0c0;">可以</span>从泛型对<span style="background-color: #c0c0c0;">获取</span>参数类型对象，但是<span style="background-color: #c0c0c0;">却不能提供</span>了。 </p>
<div class="blog_h3"><span class="graybg">受限泛型通配符</span></div>
<p>Java 5还支持<span style="background-color: #c0c0c0;">受限泛型通配符</span>的声明，所谓受限是指，变量/返回值/参数的泛型参数必须满足限制：</p>
<ol>
<li><pre class="crayon-plain-tag">Type&lt;? extends PType&gt;</pre> ：上限通配符（Upper Bounded Wildcard），表示变量的类型参数必须是PType或者PType的子类型</li>
<li><pre class="crayon-plain-tag">Type&lt;? super PType&gt;</pre> ：下限通配符（Lower Bounded Wildcard），表示变量的类型参数必须是PType或者PType的超类型</li>
</ol>
<p>在设计一个接收泛型入参的接口时，如果你需要向入参提供一个对象，可以使用下限通配符，因为这可以确保你提供的对象可以被接受：</p>
<pre class="crayon-plain-tag">public static void addNumbers( List&lt;? super Integer&gt; list ) {
    // list的实参可以是Integer，或者Number，或者Object，但是必然能容纳int类型
    for ( int i = 1; i &lt;= 10; i++ ) {
        list.add( i );
    }
}</pre>
<p>在设计一个接收泛型入参的接口时，如果你需要使用入参提供的对象，可以使用上限通配符，这样你可以假设对象的类型：</p>
<pre class="crayon-plain-tag">public static void process( List&lt;? extends Number&gt; list ) {
    // list的实参可以是Integer，或者Number，或者Double，但是都可以针对其调用Number的接口
    for ( Number n : list ) {
        n.intValue();
    }
}</pre>
<p>上面的两条，就是所谓的PECS原则：当泛型对象作为生产者（对外提供对象）时，将其泛型形参声明为上限通配符（extends）；当泛型对象作为消费者时，将其泛型形参声明为下限通配符（super） ，下面的例子有助于进一步理解PECS：</p>
<pre class="crayon-plain-tag">Value&lt;? extends Number&gt; valueExtend = new Value&lt;Integer&gt;( 1 );
//有了这个变量声明，我知道类型参数必然是Number的子类型：
Number num = valueExtend.get();
//但是，具体是Integer，还是Double，我不知道，因此我无法提供参数类型的实例
valueExtend.set( 1 ); //无法编译

Value&lt;? super Number&gt; valueSuper = new Value&lt;Number&gt;( 1 );
//有了这个变量声明，我知道类型参数必然是Number的超类型：
valueSuper.set( new Integer( 1 ) ); //那么，我当然可以提供Number的子类型给你
//但是，我却不知道类型参数的真实类型
Object obj = valueSuper.get(); //所以此变量提供的参数类型未知</pre>
<div class="blog_h3"><span class="graybg">数组与泛型</span></div>
<pre class="crayon-plain-tag">class BoundedBuffer&lt;T&gt;
{
    private T[]             items;
}</pre>
<p>但是，就像不能实例化参数类型一样，你也不能实例化参数类型的数组：</p>
<pre class="crayon-plain-tag">T t = new T();        //无法通过编译，无法提供数组需要的运行时信息
T[] ts  = new T[10];  //无法通过编译，编译器甚至无法确认T类型有默认构造器</pre>
<p>不能实例化的原因是，Java中的<span style="background-color: #c0c0c0;">数组在运行时必须知道其元素的类型</span>。上述语法无法提供这一类型信息——因为T是什么在编译时根本不知道。因此泛型数组如果需要在泛型内部初始化，必须进行变通：</p>
<pre class="crayon-plain-tag">T[] ts = (T[]) new Object[10];</pre>
<div class="blog_h3"><span class="graybg">类型擦除（Type Erasure）</span></div>
<p>注意绝大部分的参数化类型信息都在编译期间擦除了，<span style="background-color: #c0c0c0;">除了继承自泛型类的子类型，可以通过反射得到父类上的真实参数类型</span>：</p>
<pre class="crayon-plain-tag">//它的泛型父类的类型信息String不会被擦除
class StringList extends ArrayList&lt;String&gt; //和C++的模板特化有些类似
{
    private static final long serialVersionUID = 1L;
}

//可以使用下面的代码获得泛型父类的真实参数类型
ParameterizedType type = (ParameterizedType) new StringList().getClass().getGenericSuperclass();
assert ( type.getActualTypeArguments()[0] == String.class );</pre>
<div class="blog_h3"><span class="graybg">协变</span></div>
<p>需要注意泛型是“非协变”的，<span style="background-color: #c0c0c0;">参数类型不同的泛型变量不能相互转换</span>，即使这些参数类型之间存在继承关系：</p>
<pre class="crayon-plain-tag">//数组是协变的
Object oa[] = new Integer[] {};
//泛型不是，尽管泛型实参Object和Number具有父子类关系。但是List&lt;Object&gt;、List&lt;Number&gt;之类不存在父子类关系
List&lt;Object&gt; ol = new ArrayList&lt;Number&gt;(); //无法编译</pre>
<p>使用上限通配符，可以解决非协变特性导致的无法赋值问题：</p>
<pre class="crayon-plain-tag">List&lt;? extends Object&gt; ol = new ArrayList&lt;Number&gt;();  </pre>
<div class="blog_h3"><span class="graybg">泛型子类化</span></div>
<p>继承泛型类型时，可以将全部或者部分泛型参数“特化”：</p>
<pre class="crayon-plain-tag">// 完全特化
abstract class StringList implements List&lt;String&gt; {}
// 部分特化
abstract class StringKeyMap&lt;V&gt; implements Map&lt;String,V&gt; {}</pre>
<p>注意：上面的例子中，任何StringList是List的子类型，StringKeyMap是Map的子类型。在特化类型参数的同时，你可以获得重写父类逻辑的机会。</p>
<div class="blog_h2"><span class="graybg">其它新特性</span></div>
<div class="blog_h3"><span class="graybg">注解（Annotation）</span></div>
<p>注解在Java5中第一次成为可以影响编程方式的语言元素，注解可以为类、字段、方法、甚至注解本身提供元数据信息。注解的出现将很大程度上改变可配置化对XML高度依赖的现状。</p>
<p>声明注解需要使用特殊的类型说明符：<pre class="crayon-plain-tag">@interface</pre> ，例如：</p>
<pre class="crayon-plain-tag">//某些注解可以用来修饰注解类型本身
@Retention ( RetentionPolicy.RUNTIME )
//提示该注解需要在运行期保留
@Target ( ElementType.METHOD )
//提示该注解只能作用于方法上
@interface AutoExtAjaxResult
{
    //注解中可以声明多个“方法”，用来指示注解实例的属性名
    String value(); //value是一个特殊属性，如果注解只有这一个属性，在实例化注解时，可以不指定属性名
    Class&lt;?&gt; type() default App.class; //注解属性可以提供默认值
}</pre>
<p>需要注意的是，注解属性只支持有限的类型：基本类型、字符串、注解、枚举、Class，或者这些类型的一维数组。</p>
<p>要使用（实例化）注解，只需要在目标类/方法/字段的声明前面添加@注解名，并提供必要的参数，所有没有提供默认值的属性都需要提供值：</p>
<pre class="crayon-plain-tag">@AutoExtAjaxResult ( "Avalue" )
public String userInfo()
{
    return null;
}</pre>
<p>要在运行时获取某个类/方法/字段上被附加的注解，需要使用反射机制，例如：</p>
<pre class="crayon-plain-tag">App.class.getMethod( "userInfo" ).getAnnotation( AutoExtAjaxResult.class ).value(); // Avalue</pre>
<div class="blog_h3"><span class="graybg">For/In循环结构</span></div>
<p>数组以及任何实现了<pre class="crayon-plain-tag">java.lang.Iterable</pre> 接口的对象都可以使用该语法：</p>
<pre class="crayon-plain-tag">for ( char c : new char[] { 'H', 'E', 'L', 'L', 'O' } ) {
    System.out.println( c );
}
for ( String str : new ArrayList() ) {
    System.out.println( str );
}</pre>
<div class="blog_h3"><span class="graybg">自动拆装箱</span></div>
<p>现在基本类型与其包装类型的转换，可以自动进行了：</p>
<pre class="crayon-plain-tag">Number n = 1L;
Double d = 1.1d;
Boolean b = false;
boolean bp = Boolean.TRUE;
double dp = Double.valueOf( 1.1d );
long lp = new Long( 1l );</pre>
<div class="blog_h3"><span class="graybg">类型安全的枚举</span></div>
<p>Java5中，枚举被作为一种特殊的类，可以使用enum关键字来声明，枚举类型不支持创建新实例。</p>
<p>枚举类型和普通类一样可以有字段、方法、构造器等部分，特别的是，枚举实例可以对方法进行覆盖。</p>
<pre class="crayon-plain-tag">public enum Color
{
    RED( "FF0000" ),
    GREEN( "00FF00" ),
    BLUE( "0000FF" )
    {
        //每个枚举值可以覆盖方法
        public String toString()
        {
            return "00F";
        }
    };
    //和普通类一样，枚举可以具有字段、方法、构造器
    private String code;

    private Color( String code )
    {
        this.code = code;
    }

    @Override
    public String toString()
    {
        return code;
    }
}</pre>
<div class="blog_h3"><span class="graybg">可变长参数</span></div>
<p>特殊的语法可以声明方法接受不定长度的参数，变长参数的声明必须位于形参列表的尾部：</p>
<pre class="crayon-plain-tag">public static void main( String[] args )
{
    varargsMethod( 1, 2, "1", "2", "3" );
    varargsMethod0( "1", "2", "3" );
}

private static void varargsMethod0( String... strs )
{
}

private static void varargsMethod( int i, int j, String... strs )
{
}</pre>
<div class="blog_h3"><span class="graybg">静态导入</span></div>
<p>可以使用import static语法导入某些类中的静态方法，在当前文件中不需要提供这些类的名字就可以直接调用静态方法：</p>
<pre class="crayon-plain-tag">import static java.util.Collections.*;

public static void main( String[] args )
{
    sort( list ); //不需要提供Collections类名
}</pre>
<div class="blog_h3"><span class="graybg">java.util.concurrent包</span></div>
<p>这是一个Java5引入的专门用于并发编程的新包，该包主要有以下组件：</p>
<ol>
<li>支持并发的集合类型，位于java.util.concurrent包</li>
<li>原子操作类，这些原子操作类受益于JVM引入了CAS（Compare-And-Set）机制，位于java.util.concurrent.atomic包</li>
<li>新的同步原语，主要是一些锁类，位于java.util.concurrent.locks包</li>
<li>并发相关工具，例如线程池、信号量等</li>
<li>异步相关类，例如Future、Executor</li>
</ol>
<p>concurrent中的集合类图如下（深色表示JDK 1.6追加）：</p>
<p><img class="aligncenter size-full wp-image-8050" src="https://blog.gmem.cc/wp-content/uploads/2007/04/patterns_Concurrent.png" alt="patterns_Concurrent" width="662" height="846" /></p>
<p>可以看到类很多，因此不一一描述，仅将类名中单词的含义列在下表：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">单词</td>
<td style="text-align: center;">含义 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Queue</td>
<td>提供先进先出的操作接口</td>
</tr>
<tr>
<td>Deque</td>
<td>不但支持在列表的尾部操作，还支持在头部操作</td>
</tr>
<tr>
<td>Concurrent</td>
<td>表示支持线程安全的并发访问</td>
</tr>
<tr>
<td>Linked</td>
<td>表示链表结构</td>
</tr>
<tr>
<td>Array</td>
<td>表示数组结构（连续内存，高速随机访问）</td>
</tr>
<tr>
<td>Blocking</td>
<td>当集合为空，取数据操作将阻塞；当集合为满，存数据操作将阻塞</td>
</tr>
<tr>
<td>Synchronous</td>
<td>一个存操作的完成，必须依赖有一个取操作的执行</td>
</tr>
<tr>
<td>Priority</td>
<td>允许元素被优先取出</td>
</tr>
<tr>
<td>Delay</td>
<td>元素只有在超时后，才能从集合中取出</td>
</tr>
<tr>
<td>CopyOnWrite</td>
<td>一旦执行写操作，就复制底层的数据结构（例如数组），复制的成本较高，但是CopyOnWrite避免了同步开销，适用于大量读、很少写的应用场景</td>
</tr>
<tr>
<td>Navigable</td>
<td>可以导航，即根据给定的元素，寻找其最近的元素</td>
</tr>
</tbody>
</table>
<p>concurrent.atomic包中的原子操作类包括：Boolean、Integer、Long、引用的原子类及其数组类型。</p>
<p>concurrent.locks包引入了一些用于支持锁定的类和接口，改变了Java只能依靠<pre class="crayon-plain-tag">synchronized</pre> 关键字进行粗粒度同步控制的现状：</p>
<p><img class="aligncenter size-full wp-image-8062" src="https://blog.gmem.cc/wp-content/uploads/2007/04/patterns_Concurrent_Lock.png" alt="patterns_Concurrent_Lock" width="444" height="386" /></p>
<p>该包主要有三个接口：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 接口/类</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Lock</td>
<td>
<p>用于代替<pre class="crayon-plain-tag">synchronized</pre> 关键字的功能，可以关联多个<pre class="crayon-plain-tag">Condition)</pre> ，一般锁都是独占的进行共享资源的访问。该接口提供了比<pre class="crayon-plain-tag">synchronized</pre> 更灵活的功能：</p>
<ol>
<li><pre class="crayon-plain-tag">tryLock()</pre> 允许尝试性的获得锁，如果失败，立即返回而不是必须陷入等待</li>
<li><pre class="crayon-plain-tag">lockInterruptibly()</pre> 进行锁定，如果其它线程持有锁，那么让当前线程陷入等待。但是这种等待是可中断的。其它线程可以调用该线程的<pre class="crayon-plain-tag">interrupt()</pre> 中断该线程的等待</li>
<li><pre class="crayon-plain-tag">tryLock(long, TimeUnit)</pre> 允许尝试性的获得锁，如果在指定时间内还没有得到锁，则返回</li>
</ol>
</td>
</tr>
<tr>
<td>Condition</td>
<td>
<p>用于代替<pre class="crayon-plain-tag">java.lang.Object</pre> 定义的监视器方法：wait、notify和notifyAll。该接口可以把前面几个监视器方法的职责分解为多个完全不同的“条件”对象，条件对象总是和一个锁关联。</p>
<ol>
<li>当前持有锁的线程，可以等待一个条件的达成：<pre class="crayon-plain-tag">Condition.await()</pre> ，从而放弃锁，并陷入等待</li>
<li>当前持有锁的线程，可以声明一个条件已经达成：<pre class="crayon-plain-tag">Condition.signal()</pre> ，从而唤醒一个线程，或者调用<pre class="crayon-plain-tag">Condition.signalAll()</pre> 唤醒所有线程</li>
<li>被唤醒的线程将立即尝试获得锁，因为它放弃锁时，处于同步区中</li>
</ol>
</td>
</tr>
<tr>
<td>ReadWriteLock</td>
<td>
<p>维护一对相关的锁：一个用于只读操作，另外一个用于写入操作，规则如下：</p>
<ol>
<li>如果当前没有线程持有写锁，那么多个线程可以依次获得读锁</li>
<li>如果当前没有线程持有锁，那么某个线程可以持有写锁</li>
<li>如果当前有线程持有写锁，那么其它线程不能获得任何锁</li>
<li>如果当前有线程持有读锁，那么其它线程可以获取读锁，单不能获取写锁</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p>和异步编程有关的接口主要有四个：</p>
<ol>
<li>Future接口表示一个“在未来某个时间点可用的结果”</li>
<li>Callable表示一个可调用对象</li>
<li>Executor接口可以用来执行一个Runnable</li>
<li>CompletionService则可用来调用Callable并（异步）获得结果</li>
</ol>
<p>这些类的层次结构如下：</p>
<p><a href="/wp-content/uploads/2007/04/patterns_Concurrent_Async.png"><img class="aligncenter size-full wp-image-8056" src="https://blog.gmem.cc/wp-content/uploads/2007/04/patterns_Concurrent_Async.png" alt="patterns_Concurrent_Async" width="677" height="1243" /></a></p>
<p>concurrent包包含的其它工具类有：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 类</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Semaphore</td>
<td>信号量，线程可以释放信号量，亦可尝试获取信号量，信号量相当于维护了一个固定数量的许可</td>
</tr>
<tr>
<td>CountDownLatch</td>
<td>计数器，可以让某个线程在计数完毕前等待。其它线程则可以降低计数值</td>
</tr>
<tr>
<td>CyclicBarrier</td>
<td>屏障，允许多个线程相互等待（await），直到所有线程都调用了await()，所有线程才会继续运行</td>
</tr>
<tr>
<td>TimeUnit</td>
<td>这是一个枚举，可以用来进行时间单位的转换，或者让当前线程睡眠一段时间</td>
</tr>
</tbody>
</table>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/java5-new-features">Java5新特性</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/java5-new-features/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
