Friday, July 25, 2008

我的教書經驗1

http://blog.chinatimes.com/jenntser/archive/2008/07/18/299677.html

Thursday, July 24, 2008

中和華新街巷裡的南洋風光


檢視較大的地圖

小食閒走/台北異域(2003/11/18 12:56)

華新街 處處看得見緬文招牌 記者鄭郁萌╱台北報導



或許這裡就是異域,不然為何走在騎樓裡,耳邊聽到的語言全然陌生?但這裡必定是台灣,招牌上並排的緬甸文與華文,南洋的奇香異味伴著電視裡的台語……,是的,這裡便是台北的異域、寶島裡的緬甸,這裡是中和的華新街。



尋常的台灣巷道,不尋常的是遊逛時的異國風情。中和華新街一帶住了數萬緬甸華僑,一不小心就覺得有種跌進東南亞的手足無措。幸好,他們華語雖不輪轉,但是友善熱情,記者只是聊10分鐘的天,不一會兒忽然圍滿了開朗的熱情居民,爭先恐後地敘說特色鄉情。



從中和興南路轉入華新街,左右兩邊各有不同風情。左邊是亮晃晃的港式茶樓,港式茶樓兩家舖子各有兩個店面,全年無休的蒸氣氤氳。



從早上6時就開始營業,符合廣東人的「吃早茶」習俗,店舖裡不少東南亞華僑在這裡喝杯茶,用豐盛的燒賣和腸粉作早餐。在這兒任何時間都別擔心沒得吃,茶樓從早餐一路賣午餐、午茶、晚餐乃至宵夜生意到深夜。





祥鈺港式茶樓裡賣的港式點心就有40種以上,還不包括各式熱炒與煲盅。這裡賣的可沒有任何冷凍食品,大約有8個師傅親手現作,整天店舖熱氣蒸騰,光瞧著就熱鬧非凡。祥鈺港式茶樓的老闆陳清榮身型瘦小但熱情無比,是個典型的緬甸華僑,他說若遇週末假日,一整天店裡的點心總要賣上1千多籠才罷休。





這可不是棒棒腿!而是祥鈺港式茶樓的得意點心「甘蔗蝦」,中間細細的一根甘蔗嵌入蝦球中,經過快炸,甘蔗的芬芳全滲入蝦中,外皮將美味滿滿地裹起,一咬下去鮮甜香酥,這一份居然只要60元。



其餘點心也十分便宜:一份炸兩腸粉50元、蟹黃蒸燒賣45元,不算任何服務費跟茶資,大概只要市面上茶樓的一半價錢便可以吃得齒頰留香、腹撐如鼓,平日若老闆有閒的時候,他還會貼心的送上一份自製椰果涼糕,坐下來就著香片,跟你談上一談當初來台創業的艱辛路途。





右邊是一排典型的泰緬小吃和雜貨店,泰緬雜貨店MiDaMun的老闆娘張碧玉開朗活潑,店裡除了各式南洋食材、雜誌,還賣緬甸檳榔,一片荖葉講究地包上7、8種香料,一顆10元,據說比台灣檳榔耐嚼,越嚼越香。店裡還有各式的緬甸蜜餞,這可是托人從緬甸專程帶回來的喔!香辣夠勁,記者剛吃原本是辣得四處跳腳,但越嚼越有味,嚼到最後乾脆買幾包帶回去當伴手。





從華新街到忠孝街一帶約有20來家的滇緬、泰式小吃,雖然沒有華麗的裝潢,但是價格低廉美味,而且沒有別的地方比這裡還道地!這裡許多緬僑都是從雲南退駐到緬、泰的軍隊居民後裔,飲食上傳承雲南及緬甸的烹調手法:從雲南汁濃味美的過橋米線、到緬甸香辣湯鮮的魚湯麵、泰式酸辣爽口的涼拌青木瓜、檸檬魚,還有印度拉茶、椰子冰等甜點,在這兒都吃得到。







大部分的小吃店菜單是貼在牆上,以中文和緬文書寫,光坐著聽著店主交談,就有濃濃的異國風味。如果搞不懂菜名別擔心,這裡的人雖然不一定華語講得好但都很和善,看看別人吃些什麼,問問有些什麼特色菜餚,你一定會發現好吃的東西,道地料理卻一點也不破費。



或者是挑一個陽光不太烈的午後,到華新街走走,吃吃港式飲茶、再吃些滇緬小吃,到巷弄裡的東南亞香料行逛上一圈,從雜貨店帶些小伴手。這簡單的午後閒遊惠而不費,在台灣感受異國風情,在巷弄裡體驗緬甸情味,如此輕鬆,卻如此溫暖。



*小食閒走怎麼去?可從台北搭乘捷運中和線,從南勢角站4號出口出來,右轉沿著興南路約走幾分鐘,再左轉接到華新街就到囉!

哈佛研究:男性黃豆吃多精子驟減

(2008/07/24 10:13)

哈佛大學研究,豆漿吃多傷精子。 國際中心/綜合報導



很多民眾早餐都會喝一杯對身體健康的豆漿,大家都知道,黃豆裡面含有雌性激素,對停經的女性很好,但是美國哈佛團隊23日發表研究結果,男性如果攝入過量的黃豆製品,裡面的雌激素可能導致精子質量降低,甚至不孕。



在一般民眾的觀念裡,多喝豆漿有益身體健康,因為黃豆裡面含有植物雌激素異黃酮,有效調節女性停經後的不適感。但是美國哈佛大學公共衛生學院,從2000年到2006年,針對99名男性做研究,結果發現,多吃黃豆類產品的男性,精子數量竟然會大幅下降。



在研究分組裡面,平均每天攝取半杯豆漿或半份豆腐的男子,每毫升的精子數量,比沒有攝取豆漿的人,少了4100萬個精子,等於只有正常量的一半,甚至降到1/3。



此外這項研究也明確顯示,吃愈多黃豆製品的男性,精子的品質也會相對降低,因此醫生建議,在生育能力方面已經有問題的男性,最好不要吃太多的黃豆製品。

目送

2008-07-23文/龍應台作者/龍應台
出版資訊:時報出版社
ISBN:9789571348698


有些路啊,只能一個人走 我慢慢地、慢慢地瞭解到,所謂父女母子一場,只不過意味著, 你和他的緣分就是今生今世不斷地在目送他的背影漸行漸遠。





目送





華安上小學第一天,我和他手牽著手,穿過好幾條街,到維多利亞小學。九月初,家家戶戶院子裡的蘋果和梨樹都綴滿了拳頭大小的果子,枝枒因為負重而沉沉下垂,越出了樹籬,勾到過路行人的頭髮。
 很多很多的孩子,在操場上等候上課的第一聲鈴響。小小的手,圈在爸爸的、媽媽的手心裡,怯怯的眼神,打量著周遭。他們是幼稚園的畢業生,但是他們還不知道一個定律:一件事情的畢業,永遠是另一件事情的開啟。

 鈴聲一響,頓時人影錯雜,奔往不同方向,但是在那麼多穿梭紛亂的人群裡,我無比清楚地看著自己孩子的背影──就好像在一百個嬰兒同時哭聲大作時,你仍舊能夠準確聽出自己那一個的位置。華安背著一個五顏六色的書包往前走,但是他不斷地回頭;好像穿越一條無邊無際的時空長河,他的視線和我凝望的眼光隔空交會。

 我看著他瘦小的背影消失在門裡。

 十六歲,他到美國作交換生一年。我送他到機場。告別時,照例擁抱,我的頭只能貼到他的胸口,好像抱住了長頸鹿的腳。他很明顯地在勉強忍受母親的深情。

 他在長長的行列裡,等候護照檢驗;我就站在外面,用眼睛跟著他的背影一寸一寸往前挪。終於輪到他,在海關窗口停留片刻,然後拿回護照,閃入一扇門,倏忽不見。 我一直在等候,等候他消失前的回頭一瞥。但是他沒有,一次都沒有。

 現在他二十一歲,上的大學,正好是我教課的大學。但即使是同路,他也不願搭我的車。即使同車,他戴上耳機──只有一個人能聽的音樂,是一扇緊閉的門。有時他在對街等候公車,我從高樓的窗口往下看:一個高高瘦瘦的青年,眼睛望向灰色的海;我只能想像,他的內在世界和我的一樣波濤深邃,但是,我進不去。一會兒公車來了,擋住了他的身影。車子開走,一條空蕩蕩的街,只立著一只郵筒。

 我慢慢地、慢慢地瞭解到,所謂父女母子一場,只不過意味著,你和他的緣分就是今生今世不斷地在目送他的背影漸行漸遠。你站立在小路的這一端,看著他逐漸消失在小路轉彎的地方,而且,他用背影默默告訴你:不必追。


 我慢慢地、慢慢地意識到,我的落寞,彷彿和另一個背影有關。

 博士學位讀完之後,我回台灣教書。到大學報到第一天,父親用他那輛運送飼料的廉價小貨車長途送我。到了我才發覺,他沒開到大學正門口,而是停在側門的窄巷邊。卸下行李之後,他爬回車內,準備回去,明明啟動了引擎,卻又搖下車窗,頭伸出來說:「女兒,爸爸覺得很對不起你,這種車子實在不是送大學教授的車子。」

 我看著他的小貨車小心地倒車,然後噗噗駛出巷口,留下一團黑煙。直到車子轉彎看不見了,我還站在那裡,一口皮箱旁。

 每個禮拜到醫院去看他,是十幾年後的時光了。推著他的輪椅散步,他的頭低垂到胸口。有一次,發現排泄物淋滿了他的褲腿,我蹲下來用自己的手帕幫他擦拭,裙子也沾上了糞便,但是我必須就這樣趕回台北上班。護士接過他的輪椅,我拎起皮包,看著輪椅的背影,在自動玻璃門前稍停,然後沒入門後。

 我總是在暮色沉沉中奔向機場。

 火葬場的爐門前,棺木是一只巨大而沉重的抽屜,緩緩往前滑行。沒有想到可以站得那麼近,距離爐門也不過五公尺。雨絲被風吹斜,飄進長廊內。我掠開雨濕了前額的頭髮,深深、深深地凝望,希望記得這最後一次的目送。

 我慢慢地、慢慢地瞭解到,所謂父女母子一場,只不過意味著,你和他的緣分就是今生今世不斷地在目送他的背影漸行漸遠。你站立在小路的這一端,看著他逐漸消失在小路轉彎的地方,而且,他用背影默默告訴你:不必追。

共老

文/龍應台

 我們走進中環一個公園。很小一塊綠地,被四邊的摩天大樓緊緊裹著,大樓的頂端插入雲層,底部小公園像大樓與大樓之間一張小小吊床,盛著一點青翠。

 淙淙流水旁看見一塊凹凸有致的岩石,三個人各選一個角,坐了下來。一個人仰望天,一個人俯瞰地,我看一株樹,矮蹲蹲的,樹葉油亮茂盛,擠成一團濃郁的深綠。


 這三個人,平常各自忙碌。一個,經常一面開車一面上班,電話一個接一個,總是在一個紅綠燈與下一個紅綠燈之間做了無數個業務的交代。睡覺時,手機開著,放在枕邊。另一個,天還沒亮就披上白袍開始巡房,吃飯時腰間機器一響就接,放下筷子就往外疾走。和朋友痛快飲酒時,一個人站到角落裡摀著嘴小聲說話,仔細聽,他說的多半是:「屍體呢?」「家屬到了沒?」「從幾樓跳的?幾點鐘?」然後不動聲色地回到熱鬧的餐桌。人們問:「怎麼了」他說:「沒什麼。」大夥散時,他就一個人匆匆上路,多半在夜色迷茫的時候。

 還有我自己,總是有讀不完的書,寫不完的字,走不完的路,看不完的風景,想不完的事情,問不完的問題,愛不完的蟲魚鳥獸花草樹木。忙,忙死了。

 可是我們決定一起出來走走。三個人,就這樣漫無目的地行走,身上沒有一個包袱,手裡沒有一張地圖。

 然後,我就看見牠了。

 在那一團濃郁的深綠裡,藏著一隻濃郁深綠的野鸚鵡,正在啄吃一粒綠得發亮的楊桃。我靠近樹,仰頭仔細看牠。野鸚鵡眼睛圓滾滾地,也看著我。我們就在那楊桃樹下對看。

 另外兩個人,也悄悄走了過來。三個人,就那樣立在樹下,仰著頭,屏息,安靜,凝視許久,一直到野鸚鵡將楊桃吃完,吐了核,拍拍翅膀,「嘩」一下飛走。

 我們相視而笑,好像剛剛經過一個祕密的宗教儀式,然後開始想念那缺席的一個人。

 是一個陽光溫煦、微風徐徐的下午。我看見他們兩鬢多了白髮,因此他們想必也將我的日漸憔悴看在眼裡。我在心疼他們眼神裡不經意流露的風霜,那麼──他們想必也對我的流離覺得不捨?

 只是,我們很少說。

 多麼奇特的關係啊。如果我們是好友,我們會彼此探問,打電話、發簡訊、寫電郵、相約見面,表達關懷。如果我們是情人,我們會朝思暮想,會噓寒問暖,會百般牽掛,因為,情人之間是一種如膠似漆的黏合。如果我們是夫妻,只要不是怨偶,我們會朝夕相處,會耳提面命,會如影隨形,會爭吵,會和好,會把彼此的命運緊緊纏繞。


 但我們不是。我們不會跟好友一樣殷勤探問,不會跟情人一樣常相廝磨,不會跟夫婦一樣同船共渡。所謂兄弟,就是家常日子平淡過,各自有各自的工作和生活、各自做各自的抉擇和承受。我們聚首,通常不是為了彼此,而是為了父親或母親。聚首時即使促膝而坐,也不必然會談心。即使談心,也不必然有所企求──自己的抉擇,只有自己能承受,在我們這個年齡,已經了然在心。有時候,我們問,母親也走了以後,你我還會這樣相聚嗎?我們會不會,像風中轉蓬一樣,各自滾向渺茫,相忘於人生的荒漠?

 然而,又不那麼簡單,因為,和這個世界上所有其他的人都不一樣,我們從彼此的容顏裡看得見當初。我們清楚地記得彼此的兒時──老榕樹上的刻字、日本房子的紙窗、雨打在鐵皮上咚咚的聲音、夏夜裡的螢火蟲、父親念古書的聲音、母親快樂的笑、成長過程裡一點一滴的羞辱、挫折、榮耀和幸福。有一段初始的生命,全世界只有這幾個人知道,譬如你的小名,或者,你在哪一棵樹上折斷了手。

 南美洲有一種樹,雨樹,樹冠巨大圓滿如罩鐘,從樹冠一端到另一端可以有三十公尺之遙。陰天或夜間,細葉合攏,雨,直直自葉隙落下,所以葉冠雖巨大且密,樹底的小草,卻茵茵然蔥綠。兄弟,不是永不交叉的鐵軌,倒像同一株雨樹上的枝葉,雖然隔開三十公尺,但是同樹同根,日開夜闔,看同一場雨直直落地,與樹雨共老,挺好的。


你來看此花時

文/龍應台
(原刊登2008.07.10中國時報「三少四壯集」)

1

整理臥房抽屜的時候,突然發現最裡頭的角落裡有個東西,摸出來一看,是個紅色的盒子。

這一只抽屜,塞滿了細軟的內衣、手絹、絲襪,在看不見的地方卻躲著一個盒子,顯然是有心的密藏,當然是自己放的,但是,藏著什麼呢?

打開盒蓋,裡頭裹著一方黑色緞巾,緞巾密密包著的,是兩條黃金項鍊,放在手心裡沉沉的;一個黃金戒指、一對黃金耳環,一只黃金打出的雕花胸針。黃澄澄的亮彩,落在黑色緞面上,像秋天的一撮桂花。


我記得了。

她是個一輩子愛美、愛首飾的女人。那一天晚上,父親在醫院裡,她把我叫到臥房裡,拿出這一個盒子,把首飾一件一件小心地放進去,說,「給你。」

我笑著推開她的手:「媽,你知道我不帶首飾的。你留著用。」

她停下來,看著我,一時安靜下來。

我倒是看了看她和父親的大床,空著──父親不知還回不回得來。床頭牆上掛著從老家給他們帶來的湘繡。四幅並排,春蘭、夏荷、秋菊、冬梅,淡淡的緋紅黛青壓在月白色的絲綢上,俯視著一張鋪著涼席的雙人床。天花板垂下來的電扇微微吹著,發出清風的聲音。這房間,仍舊一派歲月綿長、人間靜好的氣氛。

她幽幽地說話了:「女兒,與其到時候不知道東西會流落到哪裡,不如現在清清醒醒地交給你吧。」

她把盒子放在我手心,然後用兩隻手,一上一下含著我的手,眼睛卻望向灰淡的窗外,不再說話。

把盒子重新蓋上,放回抽屜裡層,我匆匆走到客廳,拿起電話,撥她的號碼;接通了,鈴聲響起,我持著聽筒走到面海的陽台,夕陽正在下沈,海水如萬片碎金動盪閃爍。直直看出去,越過海洋越過山嶼越過雲層,一重一重飛越的話,應該是澳門、是越南,是緬甸,再超越就是印度,就是非洲了。台灣在日出的那頭,其實是我站在陽台怎麼都看不見的另一邊。我握緊聽筒,對著金色的渺茫,彷彿隔海呼喊:「是我,小晶,你的女兒──你記得嗎?」

2

我喜歡走路。讀書寫作累了,就出門走路。有時候,約個可愛的人,兩個人一起走,但是兩個人一起走時,一半的心在那人身上,只有一半的心,在看風景。

要真正地注視,必須一個人走路。一個人走路,才是你和風景之間的單獨私會。

我看見早晨淺淺的陽光裡,一個老婆婆弓著腰走下石階,上百層的寬闊石階氣派萬千,像山一樣高,她的身影柔弱如稻草。


我看見一隻花貓斜躺在一截頹唐廢棄的斷牆下,牽牛花開出一片濃青豔紫繽紛,花貓無所謂地伸了伸懶腰。

夜色朦朧裡,我看見路燈,把人行道上變電箱的影子胡亂射在一面工地白牆上,跟路樹婆娑的枝影虛實交錯掩映,看起來就像羅蜜歐對著茱麗葉低唱情歌的那個陽台。

我看見詩人周夢蝶的臉,在我揮手送他的時候,剛好嵌在一扇開動的公車的小窗格裡,好像一整輛車,無比隆重地,在為他作相框。

我看見停在鳳凰樹枝上的藍鵲,牠身體的重量壓低了綴滿鳳凰花的枝枒。我看見一隻鞋般大小的漁船,不聲不響出現在我左邊的窗戶。

我是個攝影的幼稚園大班生,不懂得理論也沒學過操作,但是跟風景約會的時間長了,行雲流水間,萬物映在眼底,突然悟到:真正能看懂這世界的,難道竟是那機器,不是你自己的眼睛、自己的心?

「你未看此花時,此花與汝同歸於寂;你來看此花時,則此花顏色一時明白起來,便知此花不在你的心外。」

這世間的風景於我的心如此「明白」,何嘗在我「心外」?相機,原來不那麼重要,它不過是我心的註解,眼的旁白。於是把相機放進走路的背包裡,隨時取出,作「看此花時」的心筆記。

每一個被我「看見」的瞬間剎那,都被我採下,而採下的每一個當時,我都感受到一種「美」的逼迫,因為每一個當時,都稍縱即逝;稍縱,即逝。

3

在台灣、香港、新馬和美國,流傳最廣的,是「目送」。很多人說,郵箱裡起碼收到十次以上不同的朋友轉來篇文章。在中國大陸,點擊率和流傳率最高的,卻是另一篇,叫做「(不)相信」。

是不是因為,對於台灣和海外的人,「相信」或「不相信」已經不是切膚的問題,反倒個人生命中最私密、最深埋、最不可言喻的「傷逝」和「捨」,才是刻骨銘心的痛?是不是因為,在中國大陸的集體心靈旅程裡,一路走來,人們現在面對的最大關卡,是「相信」與「不相信」之間的困惑、猶豫,和艱難的重新尋找?


很難說。每個人,來到「花」前,都看見不一樣的東西,都得到不一樣的「明白」。

對於行路的我而言,曾經相信,曾經不相信,今日此刻也仍舊在尋找相信。但是面對時間,你會發現,相信或不相信都不算什麼了。因此,整本書,也就是對時間的無言,對生命的目送。

4

真的,不好說。

Wednesday, July 23, 2008

Posix线程编程指南 1-5

Posix线程编程指南(1)
线程创建与取消


文档选项
将此页作为电子邮件发送




级别: 初级

杨沙洲 (pubb@163.net)

XML error: Please enter a value for the author element's jobtitle attribute, or the company-name element, or both.





2001 年 10 月 01 日

这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第一篇将向您讲述线程的创建与取消。
线程创建


1.1 线程与进程

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。

线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

1.2 创建线程

POSIX通过pthread_create()函数创建线程,API定义如下:

int pthread_create(pthread_t * thread, pthread_attr_t * attr,
void * (*start_routine)(void *), void * arg)


与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行start_routine(arg)函数。thread返回创建的线程ID,而attr是创建线程时设置的线程属性(见下)。pthread_create()的返回值表示线程创建是否成功。尽管arg是void *类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取。

1.3 线程创建属性

pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项:

__detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。

__schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过pthread_setschedparam()来改变。

__schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。

__inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。

__scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。

pthread_attr_t结构中还有一些值,但不使用pthread_create()来设置。

为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_get---/pthread_attr_set---函数。

1.4 线程创建的Linux实现

我们知道,Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和fork(),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由__clone()传入。

Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号(比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。






回页首




线程取消


2.1 线程取消的定义

一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。

2.2 线程取消的语义

线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。

线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。

2.3 取消点

根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:

pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();


2.4 程序设计方面的考虑

如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。

2.5 与线程取消相关的pthread函数

int pthread_cancel(pthread_t thread)
发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。

int pthread_setcancelstate(int state, int *oldstate)
设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。

int pthread_setcanceltype(int type, int *oldtype)
设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。

void pthread_testcancel(void)
检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。




关于作者



杨沙洲,男,现攻读国防科大计算机学院计算机软件方向博士学位。您可以通过电子邮件 pubb@163.net跟他联系。


这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第二篇将向您讲述线程的私有数据。
概念及作用

在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问,比如程序可能需要每个线程维护一个链表,而使用相同的函数操作,最简单的办法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由Posix线程库维护,称为线程私有数据(Thread-specific Data,或TSD)。






回页首




创建和注销

Posix定义了两个API分别用来创建和注销TSD:

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))


该函数从TSD池中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。

不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的,但各个线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:

static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };


创建一个TSD就相当于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。

注销一个TSD采用如下API:

int pthread_key_delete(pthread_key_t key)


这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL(见"访问")。






回页首




访问

TSD的读写都通过专门的Posix Thread函数进行,其API定义如下:

int pthread_setspecific(pthread_key_t key, const void *pointer)
void * pthread_getspecific(pthread_key_t key)



写入(pthread_setspecific())时,将pointer的值(不是所指的内容)与key相关联,而相应的读出函数则将与key相关联的数据读出来。数据类型都设为void *,因此可以指向任何类型的数据。

在LinuxThreads中,使用了一个位于线程描述结构(_pthread_descr_struct)中的二维void *指针数组来存放与key关联的数据,数组大小由以下几个宏来说明:

#define PTHREAD_KEY_2NDLEVEL_SIZE 32
#define PTHREAD_KEY_1STLEVEL_SIZE \
((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1)
/ PTHREAD_KEY_2NDLEVEL_SIZE)
其中在/usr/include/bits/local_lim.h中定义了PTHREAD_KEYS_MAX为1024,因此一维数组大小为32。而具体存放的位置由key值经过以下计算得到:
idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE
idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE


也就是说,数据存放与一个32×32的稀疏矩阵中。同样,访问的时候也由key值经过类似计算得到数据所在位置索引,再取出其中内容返回。






回页首




使用范例

以下这个例子没有什么实际意义,只是说明如何使用,以及能够使用这一机制达到存储线程私有数据的目的。

#include
#include
pthread_key_t key;
void echomsg(int t)
{
printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);
}
void * child1(void *arg)
{
int tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_setspecific(key,(void *)tid);
sleep(2);
printf("thread %d returns %d\n",tid,pthread_getspecific(key));
sleep(5);
}
void * child2(void *arg)
{
int tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_setspecific(key,(void *)tid);
sleep(1);
printf("thread %d returns %d\n",tid,pthread_getspecific(key));
sleep(5);
}
int main(void)
{
int tid1,tid2;
printf("hello\n");
pthread_key_create(&key,echomsg);
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
sleep(10);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}


给例程创建两个线程分别设置同一个线程私有数据为自己的线程ID,为了检验其私有性,程序错开了两个线程私有数据的写入和读出的时间,从程序运行结果可以看出,两个线程对TSD的修改互不干扰。同时,当线程退出时,清理函数会自动执行,参数为tid。


Posix线程编程指南(3)
线程同步


文档选项
将此页作为电子邮件发送




级别: 初级

杨沙洲 (pubb@163.net)

XML error: Please enter a value for the author element's jobtitle attribute, or the company-name element, or both.





2001 年 10 月 01 日

这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第三篇将向您讲述线程同步。
互斥锁

尽管在Posix Thread中同样可以使用IPC的信号量机制来实现互斥锁mutex功能,但显然semphore的功能过于强大了,在Posix Thread中定义了另外一套专门用于线程同步的mutex函数。

1. 创建和销毁

有两种方法创建互斥锁,静态方式和动态方式。POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下: pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; 在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。

动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) 其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。

pthread_mutex_destroy()用于注销一个互斥锁,API定义如下: int pthread_mutex_destroy(pthread_mutex_t *mutex) 销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。

2. 互斥锁属性

互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:

PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
3. 锁操作

锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。

int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)


pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

4. 其他

POSIX线程锁机制的Linux实现都不是取消点,因此,延迟取消类型的线程不会因收到取消信号而离开加锁等待。值得注意的是,如果线程在加锁后解锁前被取消,锁将永远保持锁定状态,因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。

这个锁机制同时也不是异步信号安全的,也就是说,不应该在信号处理过程中使用互斥锁,否则容易造成死锁。






回页首




条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

1. 创建和注销

条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER

动态方式调用pthread_cond_init()函数,API定义如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。

注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:
int pthread_cond_destroy(pthread_cond_t *cond)

2. 等待和激发

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)



等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。

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

激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。

3. 其他

pthread_cond_wait()和pthread_cond_timedwait()都被实现为取消点,因此,在该处等待的线程将立即重新运行,在重新锁定mutex后离开pthread_cond_wait(),然后执行取消动作。也就是说如果pthread_cond_wait()被取消,mutex是保持锁定状态的,因而需要定义退出回调函数来为其解锁。

以下示例集中演示了互斥锁和条件变量的结合使用,以及取消对于条件等待动作的影响。在例子中,有两个线程被启动,并等待同一个条件变量,如果不使用退出回调函数(见范例中的注释部分),则tid2将在pthread_mutex_lock()处永久等待。如果使用回调函数,则tid2的条件等待及主线程的条件激发都能正常工作。

#include
#include
#include
pthread_mutex_t mutex;
pthread_cond_t cond;
void * child1(void *arg)
{
pthread_cleanup_push(pthread_mutex_unlock,&mutex); /* comment 1 */
while(1){
printf("thread 1 get running \n");
printf("thread 1 pthread_mutex_lock returns %d\n",
pthread_mutex_lock(&mutex));
pthread_cond_wait(&cond,&mutex);
printf("thread 1 condition applied\n");
pthread_mutex_unlock(&mutex);
sleep(5);
}
pthread_cleanup_pop(0); /* comment 2 */
}
void *child2(void *arg)
{
while(1){
sleep(3); /* comment 3 */
printf("thread 2 get running.\n");
printf("thread 2 pthread_mutex_lock returns %d\n",
pthread_mutex_lock(&mutex));
pthread_cond_wait(&cond,&mutex);
printf("thread 2 condition applied\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main(void)
{
int tid1,tid2;
printf("hello, condition variable test\n");
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
do{
sleep(2); /* comment 4 */
pthread_cancel(tid1); /* comment 5 */
sleep(2); /* comment 6 */
pthread_cond_signal(&cond);
}while(1);
sleep(100);
pthread_exit(0);
}


如果不做注释5的pthread_cancel()动作,即使没有那些sleep()延时操作,child1和child2都能正常工作。注释3和注释4的延迟使得child1有时间完成取消动作,从而使child2能在child1退出之后进入请求锁操作。如果没有注释1和注释2的回调函数定义,系统将挂起在child2请求锁的地方;而如果同时也不做注释3和注释4的延时,child2能在child1完成取消动作以前得到控制,从而顺利执行申请锁的操作,但却可能挂起在pthread_cond_wait()中,因为其中也有申请mutex的操作。child1函数给出的是标准的条件变量的使用方式:回调函数保护,等待条件前锁定,pthread_cond_wait()返回后解锁。

条件变量机制不是异步信号安全的,也就是说,在信号处理函数中调用pthread_cond_signal()或者pthread_cond_broadcast()很可能引起死锁。






回页首




信号灯

信号灯与互斥锁和条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于"等待"操作,即资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持灯亮状态。当然,这样的操作原语也意味着更多的开销。

信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。

1. 创建和注销

POSIX信号灯标准定义了有名信号灯和无名信号灯两种,但LinuxThreads的实现仅有无名灯,同时有名灯除了总是可用于多进程之间以外,在使用上与无名灯并没有很大的区别,因此下面仅就无名灯进行讨论。

int sem_init(sem_t *sem, int pshared, unsigned int value)
这是创建信号灯的API,其中value为信号灯的初值,pshared表示是否为多进程共享而不仅仅是用于一个进程。LinuxThreads没有实现多进程共享信号灯,因此所有非0值的pshared输入都将使sem_init()返回-1,且置errno为ENOSYS。初始化好的信号灯由sem变量表征,用于以下点灯、灭灯操作。

int sem_destroy(sem_t * sem)
被注销的信号灯sem要求已没有线程在等待该信号灯,否则返回-1,且置errno为EBUSY。除此之外,LinuxThreads的信号灯注销函数不做其他动作。

2. 点灯和灭灯

int sem_post(sem_t * sem)

点灯操作将信号灯值原子地加1,表示增加一个可访问的资源。

int sem_wait(sem_t * sem)
int sem_trywait(sem_t * sem)


sem_wait()为等待灯亮操作,等待灯亮(信号灯值大于0),然后将信号灯原子地减1,并返回。sem_trywait()为sem_wait()的非阻塞版,如果信号灯计数大于0,则原子地减1并返回0,否则立即返回-1,errno置为EAGAIN。

3. 获取灯值

int sem_getvalue(sem_t * sem, int * sval)

读取sem中的灯计数,存于*sval中,并返回0。

4. 其他

sem_wait()被实现为取消点,而且在支持原子"比较且交换"指令的体系结构上,sem_post()是唯一能用于异步信号处理函数的POSIX异步信号安全的API。






回页首




异步信号

由于LinuxThreads是在核外使用核内轻量级进程实现的线程,所以基于内核的异步信号操作对于线程也是有效的。但同时,由于异步信号总是实际发往某个进程,所以无法实现POSIX标准所要求的"信号到达某个进程,然后再由该进程将信号分发到所有没有阻塞该信号的线程中"原语,而是只能影响到其中一个线程。

POSIX异步信号同时也是一个标准C库提供的功能,主要包括信号集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等)、信号处理函数安装(sigaction())、信号阻塞控制(sigprocmask())、被阻塞信号查询(sigpending())、信号等待(sigsuspend())等,它们与发送信号的kill()等函数配合就能实现进程间异步信号功能。LinuxThreads围绕线程封装了sigaction()何raise(),本节集中讨论LinuxThreads中扩展的异步信号函数,包括pthread_sigmask()、pthread_kill()和sigwait()三个函数。毫无疑问,所有POSIX异步信号函数对于线程都是可用的。

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)
设置线程的信号屏蔽码,语义与sigprocmask()相同,但对不允许屏蔽的Cancel信号和不允许响应的Restart信号进行了保护。被屏蔽的信号保存在信号队列中,可由sigpending()函数取出。

int pthread_kill(pthread_t thread, int signo)
向thread号线程发送signo信号。实现中在通过thread线程号定位到对应进程号以后使用kill()系统调用完成发送。

int sigwait(const sigset_t *set, int *sig)
挂起线程,等待set中指定的信号之一到达,并将到达的信号存入*sig中。POSIX标准建议在调用sigwait()等待信号以前,进程中所有线程都应屏蔽该信号,以保证仅有sigwait()的调用者获得该信号,因此,对于需要等待同步的异步信号,总是应该在创建任何线程以前调用pthread_sigmask()屏蔽该信号的处理。而且,调用sigwait()期间,原来附接在该信号上的信号处理函数不会被调用。

如果在等待期间接收到Cancel信号,则立即退出等待,也就是说sigwait()被实现为取消点。






回页首




其他同步方式

除了上述讨论的同步方式以外,其他很多进程间通信手段对于LinuxThreads也是可用的,比如基于文件系统的IPC(管道、Unix域Socket等)、消息队列(Sys.V或者Posix的)、System V的信号灯等。只有一点需要注意,LinuxThreads在核内是作为共享存储区、共享文件系统属性、共享信号处理、共享文件描述符的独立进程看待的。




关于作者



杨沙洲,男,现攻读国防科大计算机学院计算机软件方向博士学位。您可以通过电子邮件 pubb@163.net跟他联系。



Posix线程编程指南(4)
线程终止


文档选项
将此页作为电子邮件发送




级别: 初级

杨沙洲 (pubb@163.net)

XML error: Please enter a value for the author element's jobtitle attribute, or the company-name element, or both.





2001 年 11 月 01 日

这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第四篇将向您讲述线程中止。
线程终止方式

一般来说,Posix的线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。






回页首




线程终止时的清理

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。

最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:

void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)



pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。

pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

#define pthread_cleanup_push(routine,arg) \
{ struct _pthread_cleanup_buffer _buffer; \
_pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute) \
_pthread_cleanup_pop (&_buffer, (execute)); }



可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。

pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
/* do some work */
pthread_mutex_unlock(&mut);
pthread_cleanup_pop(0);



必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下代码段相当:

{ int oldtype;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
pthread_cleanup_push(routine, arg);
...
pthread_cleanup_pop(execute);
pthread_setcanceltype(oldtype, NULL);
}






回页首




线程终止的同步及其返回值

一般情况下,进程中各个线程的运行都是相互独立的,线程的终止并不会通知,也不会影响其他线程,终止的线程所占用的资源也并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。

void pthread_exit(void *retval)
int pthread_join(pthread_t th, void **thread_return)
int pthread_detach(pthread_t th)



pthread_join()的调用者将挂起并等待th线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。

如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的内存资源,同时也无法由pthread_join()同步,pthread_detach()执行之后,对th请求pthread_join()将返回错误。

一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收。






回页首




关于pthread_exit()和return

理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上二者由于编译器的处理有很大的不同。

在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行。

其次,在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。



这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第五篇将向您讲述pthread_self()、pthread_equal()和pthread_once()等杂项函数。
在Posix线程规范中还有几个辅助函数难以归类,暂且称其为杂项函数,主要包括pthread_self()、pthread_equal()和pthread_once()三个,另外还有一个LinuxThreads非可移植性扩展函数pthread_kill_other_threads_np()。本文就介绍这几个函数的定义和使用。

获得本线程ID


pthread_t pthread_self(void)

本函数返回本线程的标识符。

在LinuxThreads中,每个线程都用一个pthread_descr结构来描述,其中包含了线程状态、线程ID等所有需要的数据结构,此函数的实现就是在线程栈帧中找到本线程的pthread_descr结构,然后返回其中的p_tid项。

pthread_t类型在LinuxThreads中定义为无符号长整型。






回页首




判断两个线程是否为同一线程


int pthread_equal(pthread_t thread1, pthread_t thread2)

判断两个线程描述符是否指向同一线程。在LinuxThreads中,线程ID相同的线程必然是同一个线程,因此,这个函数的实现仅仅判断thread1和thread2是否相等。






回页首




仅执行一次的操作


int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。

#include
#include
pthread_once_t once=PTHREAD_ONCE_INIT;
void once_run(void)
{
printf("once_run in thread %d\n",pthread_self());
}
void * child1(void *arg)
{
int tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_once(&once,once_run);
printf("thread %d returns\n",tid);
}
void * child2(void *arg)
{
int tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_once(&once,once_run);
printf("thread %d returns\n",tid);
}
int main(void)
{
int tid1,tid2;
printf("hello\n");
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
sleep(10);
printf("main thread exit\n");
return 0;
}


once_run()函数仅执行一次,且究竟在哪个线程中执行是不定的,尽管pthread_once(&once,once_run)出现在两个线程中。

LinuxThreads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执行过。如果once_control的初值不是PTHREAD_ONCE_INIT(LinuxThreads定义为0),pthread_once()的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER(0)、IN_PROGRESS(1)、DONE(2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0。






回页首




pthread_kill_other_threads_np()


void pthread_kill_other_threads_np(void)

这个函数是LinuxThreads针对本身无法实现的POSIX约定而做的扩展。POSIX要求当进程的某一个线程执行exec*系统调用在进程空间中加载另一个程序时,当前进程的所有线程都应终止。由于LinuxThreads的局限性,该机制无法在exec中实现,因此要求线程执行exec前手工终止其他所有线程。pthread_kill_other_threads_np()的作用就是这个。

需要注意的是,pthread_kill_other_threads_np()并没有通过pthread_cancel()来终止线程,而是直接向管理线程发"进程退出"信号,使所有其他线程都结束运行,而不经过Cancel动作,当然也不会执行退出回调函数。尽管LinuxThreads的实验结果与文档说明相同,但代码实现中却是用的__pthread_sig_cancel信号来kill线程,应该效果与执行pthread_cancel()是一样的,其中原因目前还不清楚。




关于作者



杨沙洲,男,现攻读国防科大计算机学院计算机软件方向博士学位。您可以通过电子邮件 pubb@163.net跟他联系。

Posix线程编程指南(4)

Posix线程编程指南(4)



2001 年 11 月 01 日
这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第四篇将向您讲述线程中止。
线程终止方式
一般来说,Posix的线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。
回页首
线程终止时的清理
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。
在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg) \
{ struct _pthread_cleanup_buffer _buffer; \
_pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute) \
_pthread_cleanup_pop (&_buffer, (execute)); }
可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
/* do some work */
pthread_mutex_unlock(&mut);
pthread_cleanup_pop(0);
必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下代码段相当:
{ int oldtype;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
pthread_cleanup_push(routine, arg);
...
pthread_cleanup_pop(execute);
pthread_setcanceltype(oldtype, NULL);
}
回页首
线程终止的同步及其返回值
一般情况下,进程中各个线程的运行都是相互独立的,线程的终止并不会通知,也不会影响其他线程,终止的线程所占用的资源也并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。
void pthread_exit(void *retval)
int pthread_join(pthread_t th, void **thread_return)
int pthread_detach(pthread_t th)
pthread_join()的调用者将挂起并等待th线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。
如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的内存资源,同时也无法由pthread_join()同步,pthread_detach()执行之后,对th请求pthread_join()将返回错误。
一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收。
回页首
关于pthread_exit()和return
理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上二者由于编译器的处理有很大的不同。
在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行。
其次,在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。
关于作者
杨沙洲,男,现攻读国防科大计算机学院计算机软件方向博士学位。您可以通过电子邮件 pubb@163.net跟他联系。

Tuesday, July 22, 2008

周星馳以「小強精神」出頭天

本篇文章摘自:商業周刊第 1078 期作者:曾如瑩



做人如果沒有夢想,跟鹹魚有什麼分別?這句廣為流傳的台詞,像幫浦一樣,為洩氣的人持續打氣。(攝影●陳炳勳)
他,從小在廟街長大,中學畢業後,沒有工作、只會做夢;好不容易當上演員,卻面臨8年跑龍套的命運。但即使在命運惡神面前,他卻能保留一絲笑意,持續往上爬,成為現在家喻戶曉的喜劇之王——周星馳。
二十五年前,在電視劇「射雕英雄傳」裡,一身破爛衣、塗黑了臉,他是一掌被打死的臨時演員,那時,他沒有一句台詞。 十八年前,在電影「賭俠」裡,他是一個穿著西裝、梳著油頭,有特異功能可以看穿底牌,贏得賭局的大陸仔。這一年走紅台港,他的經典無厘頭台詞是:「戲法人人會變,但不是每個人都能拿A士。」 今年七月四日,他穿上合身灰色西裝,這次身分是獨立製片人,出席國泰投信娛樂基金座談會。這次他不講台詞,而是像個企業CEO一樣,大談「控制成本要如同遵守法律,」讓電影投資能獲利兩成。 由臨時演員、電影明星,到同於企業CEO的製片人,走過人生三階段,周星馳事業規模一再擴大,從一個月薪水港幣兩千元,到片酬港幣千萬元以上,最近更是上億美元票房製片人。 近三部由他一手演、編、導的電影,「少林足球」、「功夫」和「長江七號」,三部成本共約四千六百萬美元的片子,就贏得一億八千萬美元的全球票房,已經跨越好萊塢大片基本的收益門檻一億美元,晉升為「億元俱樂部」一員,奠定了他喜劇天王的地位。 不只拍電影,他進軍日本,監製電影「少林少女」,二十一世紀福斯電影公司也邀請他擔任「七龍珠」監製,他已經從亞洲市場走向國際舞台。 二○○三年,他獲選為《時代》雜誌亞洲英雄,《時代》形容「如果香港有卓別林的話,非他莫屬」。一九三○年代,美國經濟大恐慌時,卓別林苦中帶笑的電影撫慰人心,周星馳電影裡小人物奮鬥記,也有同樣的效果。 周星馳,在許多人眼中,早已不是演員,而是個成功的娛樂企業人。然而,該如何定位他的成功?財富?影迷人數?影響力? 當面對《商業周刊》的採訪團隊,才四十六歲就頂著一頭白髮,比電影裡嚴肅多倍的周星馳,這樣看他自己:「我個人喜愛小人物,這我最熟,因為我就是小人物。」「在困境當中必須具備忍耐的素質。」「小人物要做很多、很多努力,才有那一點點成功。」
【活動】痛苦指數快破表,星爺語錄讓大家一起抗低潮!
合作多年的演員田啟文認為,他就是一個小人物,相信要比別人更努力工作、賦予工作生命,就一定會成功。 築夢‧出身貧窮,只有中學畢業 參加演員考試,因為不夠帥被刷掉 在電影「唐伯虎點秋香」裡,周星馳戲稱蟑螂為小強,影迷以此大做文章,強調小強打不死的耐力。採訪過周星馳之後,我們發現他也有一種「打不死的努力」,不斷向上、在專業上提升,我們稱這是「小強精神」。 周星馳家境並不富裕,排行老二,有三個姊妹,一家六口,就擠在九龍的木板房裡。小時候母親為了養大四個孩子,必須到飯店做女工,把小孩寄託給廟街做生意的外婆。 中學畢業後,周星馳學校成績不好,沒考上會考,還經歷半年多找不到工作,當母親和姊姊外出工作養家,他則是當了半年的米蟲,他形容「在家裡,就是打拳、睡覺。睡完又打,打完又睡覺,」根本沒有一技之長。 當時想:「這種一事無成的人,就是愛做夢、胡思亂想,除了當演員還能當什麼?」他自嘲的說。當時香港無線電視台(TVB)招考演員,周星馳就拖著中學同學梁朝偉,一起報名。 為了讓面試官留下好印象,身高一百七十四公分的周星馳,前一天還特地花錢買了雙昂貴的增高鞋,結果,放榜後,陪考的梁朝偉考上訓練班,而穿了增高鞋的周星馳,卻因長得不夠帥,考官根本懶得看他第二眼。 直到鄰居告訴他TVB將招考夜間部訓練班,他才又再接再厲,報考成功。 苦蹲‧主持兒童節目兼跑龍套 就算一出場就死掉,也要研究死法 命運之神卻一再捉弄他,畢業後,雖然想演戲,卻被分到兒童節目「四三○穿梭機」,播出時間是下午四點半的冷門時段。不過,這一待就是四年。 儘管如此,他還是在兒童節目上費盡心思求表現;為了主持好節目,就著家裡昏黃的小燈,在家人熟睡時,他橫靠在床上,研究著戲劇大師史坦尼斯拉夫斯基(Constantin Stanislavski)的書《演員的自我修養》。「我還研究關於方法演技,把它放到兒童節目裡面,還在兒童節目裡做出很多劇本沒有的創意啊!做得這麼完美,我以為肯定要加人工(薪資),還有很多機會啊,很多人會注意我啊,最後是沒有,完、全、沒、有。」他回憶。 期望通通落空,小孩子根本不懂得什麼方法演技,他認清現實,卻也沒有放棄:「我就知道,我很努力,但是成果可能不會成正比例,非得繼續再加一點(努力)。」 連續四年的時間,每天錄完「四三○穿梭機」,他都認真揣摩隔天的龍套角色(編按:擔任「四三○穿梭機」主持人時,同時在多部連續劇出任臨時演員),就算沒有台詞,演出一出場就死掉的角色,他都研究出一套死法。 在港劇「射雕英雄傳」,他一出場,就被梅超風一掌打在天靈蓋上死掉,出場時間三秒鐘,戴著一頭假長髮,臉上塗一層灰,穿著深褐色破爛衣服,再加上演出時間是晚上,觀眾根本不會對他有印象,他卻帶著傻氣要求導演:「可以讓我接一招再被打死嗎?這比較合理,哪有人不反抗? 」 為了一個小小的表情,覺得自己表現得不好,他要求導演重來,導演沒有理會他,他不死心,坐車回電視台,在門口守著,導演出來後,一個箭步衝到前面,「那種感覺好像要跪下來求他,『你試試看給我一個機會,我再做一次……。』」他笑說:「其實我現在想起來根本沒什麼分別的,就是對自己的一個要求。」 擔任周星馳國語配音員的石班瑜觀察:「他是個很堅強、專注在走自己路的人。」 就像是他在自導自演「喜劇之王」裡的對白,「臨時演員也是演員,雖然是扮演路人甲乙丙丁,也是有生命,有靈魂的。」在他眼中,演出一秒,也是演員。 為達到演員的專業,就算沒有人在乎,他對著鏡子,一種情緒,練出好幾種表情。緊張也有八種緊張的表情,兒子出生的緊張、中六合彩的緊張、老婆生孩子的緊張……,雖然都是緊張,卻因為臉部表情的微妙,有所改變,「非得要這樣子才能成功,一個感覺要有八個表情(加重語氣),三個可能都不夠,四個可能都不夠,非常嚴格,才能做出一點點成功的事情。」他強調。 四年的主持生涯,他走得很辛苦,當梁朝偉已經是TVB力捧的五虎將之一,他則在練習不同表情,每個月只領港幣兩千元的薪資。 一次又一次打擊,難道不覺得苦?周星馳一派輕鬆的回答:「我不從苦的角度看事情。」 在兒童節目待了四年後,終於有機會轉入劇組演出「生命之旅」,即便在劇中仍然是小角色,同班同學吳鎮宇的名字都列得比他前面,他卻很珍惜演出機會,把劇本都翻爛了。 小強也有出頭天的時候。一九八八年,周星馳終於被李修賢相中,演出電影「霹靂先鋒」,一舉獲得台灣金馬獎最佳男配角。回到香港後,TVB讓他擔任電視劇「蓋世豪俠」男主角,他在電視台給他的劇本上,額外加入自己的對白,詮釋那個貪生怕死的主角,一遇到危險,就說出一句「不如大家坐下來,喝杯茶、吃個包子慢慢說。」這句話成為當時香港流行語,躍上大銀幕,隱藏在他身邊的星光,逐漸亮起。 出頭天‧無厘頭幽默打中人心 演活小人物形象,電影台詞變流行語 累積八年的努力,一九九○年,幸運女神由此降臨,無厘頭的演出大受歡迎。一年內他接拍十一部片子,平均一個多月,出品一部,「賭聖」、「賭俠」和「逃學威龍」每齣香港票房都破港幣四千萬元,一再打破最高紀錄,一時之間從香港港督到廟街小販,都認識這個滿嘴無厘頭的演員。 他演出的角色無論是「凌凌漆大戰金鎗客」裡的豬肉販,或是「逃學威龍」的臥底警察,都是小人物,但是在二十年間,香港出品的電影裡,有六百部以上都是以小人物當主角,周星馳有何能耐讓觀眾埋單? 別人演小人物,總是一本善良,他演的小人物則有喜怒哀樂、亦正亦邪,就如同你、我,大家都有弱點、有優點。像是在「九品芝麻官」裡,他演的包龍星是小貪官,別人拿出一個「廉」字跟他說教,包龍星則回答:「怎麼看都是一個窮字。」這句話,是許多人想在心裡、沒說出口的話,他幫忙說了。 再來,他在大家熟悉的日常生活事物,發揮搞笑創意,折凳、泡麵、蟑螂,都是他搞笑的對象,讓觀眾覺得又熟悉、又認同。 「唐伯虎點秋香」裡為了贏得秋香的同情,他飾演的唐伯虎,一把抓起蟑螂哭喊著:「小強!你怎麼了小強?小強你不能死啊!我跟你相依為命、同甘共苦了這麼多年,一直把你當成親生骨肉一樣教你養你,想不到今天白髮人送黑髮人……。」從此蟑螂的名字就叫做小強,一舉改變了數百年來大家對蟑螂的稱呼。 他更不吝於從自己過去的痛點裡找到歡笑。「逃學威龍」裡,學生周星星其實是個臥底警察,老師對學生扔板擦、丟粉筆,周星星都可以順利接到板擦和粉筆,這一些都是小時候功課不好、調皮的他,真實經歷過的處罰招數。 不過,看似無厘頭的創意,並非在片場裡即興創作,而是經過數百次的演練,他說:「有時候得要想一百個創意,才找到一個好的創意。」 從個性、熟悉的事物、到利用大家曾經歷的痛苦搞笑,一層層卸掉觀眾心防,引發共鳴,讓周星馳無厘頭的幽默感打中人心,影迷背誦劇中台詞,逐漸流傳。 巔峰轉型‧投入劇情編導 從痛點找笑點,反向詮釋人生磨難 然而不久,根據香港影業協會統計,一九九三到一九九六年間,港片開始退燒,觀看電影人次衰減四成,觀眾對於無厘頭的新鮮感不再,這段期間,周星馳也只有「食神」賣出超過港幣四千萬元票房。甲上娛樂總經理陳鴻元說,像是周星馳拍的「喜劇之王」,台灣幾乎沒有人敢發行。 打不死的他,卻在這段期間成立公司,由演員周星馳,走向導演周星馳,而且在這段時間的作品中,更深刻加入他對人生的詮釋。 無論是「喜劇之王」、「少林足球」、「功夫」等,少了無厘頭對白,他加入劇情。陳鴻元說:「他說故事的方式很特殊,表面上是喜劇,骨子裡卻是在談婚姻關係、兄弟之情,」還在能引起共鳴的小人物形象,以及將痛苦轉為笑容兩點上,渲染情緒。 「少林足球」裡,一個兩百磅的胖子;一個小腹突出、禿頭的中年男子;甚至是跛腳的教練,加上一位光頭爛臉、連足球規則都搞不懂的太極拳女生,這一群在現實生活中不可能踢足球的人,都能夠贏得大獎。 「長江七號」裡,窮到連家門都沒法關好的父子,餐桌只是牆上的一塊木板,一邊吃飯,蟑螂在牆上亂竄,小孩居然以一手打蟑螂為樂;一般人認為是苦,他反而用放大鏡看裡面的樂。 「一對父子很窮、很可憐,在家裡就哭,我覺得這個我不喜歡。我就相反(的處理),在很困難的日子,但是他們都『哇!很開心啊』(眉飛色舞),所謂很慘的東西,你用比較相反的手法去表達,效果會更好,會更難忘。」周星馳說。 「少林足球」裡,他演一個職業是清潔工的少林弟子,以推廣少林功夫為職志,戲中壯著胸脯直問:「做人如果沒有夢想,跟鹹魚有什麼分別?」像是幫浦一樣,他戲劇裡的一句話,往往能為被現實洩了氣的人們打氣。 許多在痛點找到笑點的橋段,正是周星馳大量從過去經歷過的困苦環境裡,萃取的戲劇元素。 小時候他跟著外婆在廟街賣小剪刀,廟街就等於當時平民的夜總會,小孩子們都在路邊玩耍,看到蟑螂也不害怕,反而追著玩,後來成為「長江七號」裡打蟑螂的戲碼。他說:「很多時候,什麼叫苦?什麼叫樂?完全是你自己(用)什麼角度看事情。」 不過,為了從現實生活的磨難,和情感裡抽絲剝繭,找出笑點,周星馳將大量的時間用來思考。 面臨困境‧一度被質疑已經過氣 熬過轉型陣痛期,連知識份子也迷他 香港蘭桂坊是狗仔隊最密集的地方,藝人常在此處酒吧玩樂,被稱為「埋堆」,但在八卦雜誌上,卻很少看到周星馳飲酒作樂的照片,每天就是來回家裡和公司。
石班瑜說:「他不太和香港演藝圈的人和在一起,平時就是騎單車,穿個球鞋就來上班。」周星馳形容自己花很多時間在思考,「因為做導演,每天吃飯要想,走路要想,坐車要想,洗澡要想,連上廁所也要想。」像是「齊天大聖東/西遊記」裡一段談愛情的對白,就是在坐車時想出來的。 武術結合足球的創意也是他苦思而來的,「在日本、歐洲,那麼熱愛足球,你把運動加上一些他們認知、但是又從來沒有見過的東西,肯定會接受。」想著想著,頭髮都想白了,四十六歲,他有著一頭比同年紀人更多的斑白頭髮和鬍髭。 轉向偏重劇情,並非沒有風險,「齊天大聖東/西遊記」是周星馳參與編劇至深的片子,但是上下兩集加起來,不過是港幣四千六百萬元的票房,還不如他和梅豔芳主演的「威龍闖天關」。田啟文回憶,當時因為受限於預算,無法用太多特效,因此轉向愛情劇,但是卻太前衛了,過了幾年後,「齊天大聖東/西遊記」才在中國延燒。就連「喜劇之王」也不到港幣三千萬元票房,當時甚至有周星馳時代已過的說法。 但短暫的挫折就像是乾柴,讓心中的火燒得更旺。和過去一年至少拍一部片不同,他用三年孕育一部電影,「齊天大聖東/西遊記」之後,「少林足球」和「功夫」誕生了。這兩部片子,讓周星馳不再只是無厘頭教主,連許多知識份子都站出來承認是他的粉絲。文化評論家南方朔寫著:「周星馳無疑是當代首屈一指的鬧劇巨將,在我的評價裡,甚至認為他比金凱瑞,已不只是領先一籌而已。」 「一定要很努力、很努力,才能有一點點成功」的價值觀,在周星馳製片的創意中,更淋漓盡致的發揮。 在「功夫」裡,一幕火雲邪神用兩指夾住射出的子彈,不到三秒的鏡頭,但在拍攝時,為了要讓火雲邪神的頭髮,隨著子彈的風速飄起來,必須開槍、開電風扇、演員舉起手三者同步,為了這三項要素,他拍了十幾次才成功。星輝海外製片王雅琳說:「他是個一絲不苟的電影人,有時候大家懷疑真的要做成那樣嗎?最後效果出來,都證明他是對的。」 躍上國際‧提升到專業境界 要求精準度,電影看過上千遍才上映 當上導演後,周星馳不僅自己要演得精準,其他演員也必須精準,就像是「少林足球」裡說道:「足球,不是一個人就可以踢的。」戲,也不是只有主角演好就好。 田啟文認為,喜劇不同於偶像劇,必須根據臨場的感覺,嘗試不同的效果,周星馳要求精準度比別人嚴格很多。 拍「少林足球」時,飾演三師兄「金鐘罩鐵布衫」的田啟文,有一場被丟雞蛋到嘴裡的戲,電影裡只出現兩次,但是拍攝時,卻整整丟了三盤、七十二顆雞蛋。「他就是不停的拍、再看,做到最好。」在掌鏡的螢幕裡,連後面的臨時演員都必須有表情,像「少林足球」裡後面吃麵的大嬸,都要做足表情。 只要一上片場,對他來說就是打仗,每個細節都要做到最好,周星馳解釋,「這個自我要求的精神,非得要做到精準的感覺。」上映之前,周星馳至少看過一千遍以上,笑稱「看到都想吐」,因為要求專業,很多合作的人反倒認為他太過嚴肅或者難以溝通。 陳鴻元說,「少林足球」、「功夫」和過去比較粗糙的戲劇不同,把周星馳提升到另一個境界,強調劇情比起無厘頭對白,更容易推廣到國際市場。「少林足球」一打入歐洲市場,法國影評寫著「稍微天真,但富有自由行走的創意和品質保證的智障笑點」,周星馳馬上成為顯學。但很可惜,因為被中國官方認為對少林功夫不敬,而遭禁演。 不跌倒怎麼能夠再爬起來?在「長江七號」裡,他不氣餒,針對中國市場開發劇情,講述民工在困苦中,仍然樂觀的精神,讓他和中國導演馮小剛拍的「集結號」,成為人民幣兩億元票房的導演。 不行,就再重來,跌倒也要抓起一把沙的性格,讓周星馳不斷在演藝之路,逐漸在專業中昇華,眼神也由過去的稚嫩,轉為銳利。近期他計畫重拍過去曾經在票房上敗陣的「齊天大聖東/西遊記」,「不OK就再重來。」他點亮桌前的燈,陷入思考,想著下一輪挑戰。 在真實人生,周星馳不變的是,他仍然守住對小人物的深深認同。 每天,周星馳早上從香港寶雲道的家裡,騎單車到位於中環的公司上班,這段路,有上坡、下坡。他沒有忘記自己的人生也不斷上坡、下坡,由一事無成的人,爬上國際舞台。 他就是個小人物,相信最簡單的:只要比別人更努力,一定會成功,「沒有不OK的,一定要OK。」周星馳聳聳肩,繼續前進。

怪怪俏女師 捏男生乳頭體罰

2008-07-22
【潘杏惠/南縣報導】
漂亮的已婚女導師,當著所有同學的面,用手隔著衣服捏國一男生的乳頭,是體罰還是性騷擾?離譜的是,這名台大歷史系畢業的劉姓導師還邊捏邊說:「我要把你捏到A罩杯,而且我不要捏女生,因為我不要她們的比我大!」
現年卅三歲的南縣某國中一年級劉姓女導師,任職一年多來,懲處學生的方式竟是當著所有同學的面,隔衣捏男同學的乳頭。教育單位六月初進行教學正常化抽樣問卷調查,有學生無意間透露,這才揭露教師不當體罰且涉嫌性騷擾的動作。
昨日該校暑期輔導開始,學生受訪時指出,擁有台大歷史系學歷的女導師,針對調皮搗蛋或課業成績不理想的同學,動輒以捏人方式懲處,但對女同學的處罰則僅是捏臉而已。
處罰方式離譜 男同學困擾難堪
曾多次被捏過乳頭的男生表示,他們當下的感覺很不舒服;在場目睹的同學有人嘻笑以對,有人悶不吭聲,讓他更難堪的是,還得強顏歡笑。
他曾經央求老師,能不能比照女生捏臉就好,老師最後才罷手。他認為,懲處學生有很多種方式,就算要這麼做,也應該私下處理;有同學曾將事情轉告母親,得到的回應是等升上二年級有類似情況再來處理。
冬天掀衣摸背 在家刷兒子股溝
有同學則說,冬天時女導師還會把學生的衣領掀開,整隻手伸進背部撫摸,以冰冷的觸感懲罰學生;甚至還曾經在課堂上公然表示,她在家中也會跟孩子的身體玩「刷卡遊戲」,也就是伸手往屁股溝「刷」下去。
還有學生說,女導師常有唐突的言行,包括當著所有學生面前表示,女生應該添購半罩式胸罩,穿起來比較舒服,讓男生聽得面面相覷;甚至還會對著男同學稱呼「阿娜達」,並說「我有老公,不要再引誘我了。」
要學生學狗啃 不當體罰涉性騷
另外有同學表示,五月卅日當天上中國歷史課時,有學生突然冒出一句「阿共仔吃屎」,女導師立刻回應「狗才會吃屎」,旋即要求學生咬鉛筆學狗啃骨頭模樣。
教育單位六月初至該校進行教學正常化抽樣問卷調查,「捏人算不算體罰啊?」一群學生在填寫有關師長體罰問題時提出疑問,幾經追問,這才意外揭發教師不當體罰且有性騷擾之嫌的行為。
教部、性平協會:已構成性騷擾
【潘杏惠/南縣報導】
教育部人權教育諮詢小組委員林佳範教授指出,劉姓女導師行為,就法而言已構成性騷擾,且假借輔導管教名義行使教師權力,企圖合理化侵犯孩子身體自主權的行為,實在看不出究竟想讓學生獲得什麼,校方應立刻依照《性別平等教育法》開始調查程序。
林佳範表示,女導師的行為,不但是不當管教,且以充滿性意味的肢體動作,侵犯孩子的身體,加上性別差異的戲謔言語,構成性騷擾行為;孩子正值青春期,性啟蒙一片懵懂,在錯誤示範下,不論是深受其害或目睹的學生,皆容易產生任何有權力的人可任意侵犯自己身體的誤解。
台灣性別平等教育協會秘書長賴友梅則說,女導師個人認為體罰是在輕鬆愉快氣氛中進行,當下沒有同學反應不舒服,其實嚴重暴露師生間不對等的權力關係,讓未成年相形較弱勢的學生面臨難以啟齒也不敢反應的困境,未必代表學生就認同這樣的行為。
賴友梅說,以日常生活經驗為例,許多性騷擾的加害者,通常都覺得事件本身就是輕鬆愉快,以為拍拍別人屁股是在打招呼,這只是行為人界定,卻忽視被騷擾者的主觀感受。
奇美醫院精神科主治醫師林健禾表示,如果女導師隔衣捏男生乳頭經查屬實,即為極端不當管教;可能必須探究在年幼成長過程,是否因性別差異,產生男性調皮搗蛋印象,且因此受到處罰,進而有樣學樣。另外還有一種可能則是,導師認為年輕學子間常有肢體接觸,嘻嘻哈哈、無傷大雅,因此選擇以此種方式懲處同學。
師:「玩樂默契」 懲處方式有瑕疵
【潘杏惠/南縣報導】
「我們班比較搞笑,我不想用嚴肅的方式去處罰學生,盡量在輕鬆愉快氣氛下進行,師生間彼此有『玩樂默契』,當下並沒有學生反應不舒服。」劉姓女導師承認,她確實對學生隔衣做出捏乳頭動作,也未否認說過捏成A罩杯以及不想捏女同學的話;她認為懲處方式確有瑕疵,一切靜待校方調查,願意為不當行為認錯。
劉姓老師接受電話訪問表示,到學校教書就是去愛學生,傳統的師生關係總是充滿距離感,她一直希望師生間能親密融洽,建立良好互動,為此付出很多心思。
劉姓女導師指出,她帶的是比較搞笑的班級,倘若以嚴肅方式懲處,恐易引起反感,所以盡量在輕鬆愉快氣氛下進行;她承認確實對學生隔著衣服做出捏乳頭動作,不過當下並沒有同學反應不舒服,而且與班上學生的感情都很要好,彼此有「玩樂默契」。她也不否認曾說過捏成A罩杯以及不想捏女同學的話。
講著講著,她的丈夫逕自接過電話表示,不希望太太再談下去。他還說,日前夫婦倆才在電視上看過某教師以夾子夾學生乳頭的新聞,彼此討論覺得不妥,相信太太會拿捏管教界線。
校長指出,劉姓老師教學認真,對學生要求高,為人熱情,跟其他老師的相處一切正常。劉老師對學生做出捏乳頭行為,確實不妥;至於學生指稱的其他不當體罰行徑,將先由校方內部調查才能釐清真相,不過他早已預定廿一日前往日本旅遊,將委請總務主任召開調查會議,他一周後返國會盡速處理。