2011-08-06 11 views
11

flock() के लिए PHP का प्रलेखन पृष्ठ इंगित करता है कि आईआईएस के तहत उपयोग करना सुरक्षित नहीं है। अगर मैं सभी परिस्थितियों में flock पर भरोसा नहीं कर सकता, तो क्या मैं एक और तरीका सुरक्षित रूप से एक ही चीज़ प्राप्त कर सकता हूं?PHP झुंड() वैकल्पिक

+0

'झुंड()' उपयोग करने के लिए भी असुविधाजनक है यदि आपको 0-लंबाई वाली फ़ाइलों को पढ़ने से बचने की आवश्यकता है। ऐसा इसलिए है क्योंकि 'झुंड()' को केवल _after_ कहा जा सकता है, एक फ़ाइल बनाई गई है, एक नई फ़ाइल बनाना और इसे परमाणु रूप से लिखना असंभव है। – rustyx

+0

इसके अतिरिक्त मैंने पढ़ा है कि अनिवार्य फ़ाइल लॉकिंग को लिनक्स पर बहिष्कृत किया गया है, इसलिए झुंड वास्तव में आदर्श नहीं है (सेटअप करने के लिए थोड़ा सा काम भी) – Antony

उत्तर

7

सभी काल्पनिक संभावित परिस्थितियों में सुरक्षित रूप से इसे प्राप्त करने के लिए कोई विकल्प उपलब्ध नहीं है। यह कंप्यूटर सिस्टम और the job is not trivial for cross-platform code के डिजाइन द्वारा है।

यदि आपको flock() का सुरक्षित उपयोग करने की आवश्यकता है, तो इसके बजाय अपने आवेदन के लिए आवश्यकताओं को दस्तावेज़ करें।

वैकल्पिक रूप से आप अपना लॉकिंग तंत्र बना सकते हैं, हालांकि आपको यह सुनिश्चित करना होगा कि यह परमाणु है। इसका मतलब है, आपको लॉक के लिए परीक्षण करना होगा और यदि यह अस्तित्व में नहीं है, तो लॉक स्थापित करें, जबकि आपको यह सुनिश्चित करने की आवश्यकता है कि कुछ भी लॉक इन-बीच प्राप्त नहीं कर सकता है।

यह लॉक का प्रतिनिधित्व करने वाली लॉक-फ़ाइल बनाकर किया जा सकता है, लेकिन केवल तभी होता है जब यह अस्तित्व में न हो। दुर्भाग्य से, PHP इस तरह से एक फ़ाइल बनाने के लिए इस तरह के एक समारोह की पेशकश नहीं करता है।

वैकल्पिक रूप से आप mkdir() के साथ एक निर्देशिका बना सकते हैं और परिणाम के साथ काम कर सकते हैं क्योंकि यह निर्देशिका बनने पर true लौटाएगी और false यदि यह पहले से मौजूद है।

+0

यह सही है। मैंने लॉकफाइल के बारे में सोचा था, लेकिन PHP में उन्हें लागू करने में समस्याएं हैं। मैंने पंक्ति लॉकिंग के साथ एक डेटाबेस का उपयोग करने के बारे में भी सोचा था, जिसे "लॉक" ध्वज सेट और अनसेट करना है, लेकिन यह धीमा है और मजबूत नहीं है।हालांकि, मैं लॉकफाइल के स्थान पर एक निर्देशिका का उपयोग करने के बारे में सोचा नहीं था! धन्यवाद! – Matty

+0

लेकिन यह केवल काम करता है * अगर * 'mkdir()' निर्दिष्ट मूल्यों को वापस कर रहा है। मुझे नहीं पता कि क्या * सभी * प्लेटफॉर्म/फाइल-सिस्टम संयोजनों के लिए मामला है। – hakre

+0

'mkdir()' ठीक से व्यवहार कर रहा है - कम से कम लिनक्स पर। मैं इसे विंडोज सर्वर पर परीक्षण करने जा रहा हूं। यदि यह अपेक्षित काम नहीं करता है, तो मैं इसे अपडेट करने के लिए वापस आऊंगा। – Matty

1

मेरा प्रस्ताव flock() के बजाय mkdir() का उपयोग करना है। यह पढ़ने/लिखने के कैश मतभेद दिखाने के लिए एक वास्तविक दुनिया उदाहरण है:

$data = false; 
$cache_file = 'cache/first_last123.inc'; 
$lock_dir = 'cache/first_last123_lock'; 
// read data from cache if no writing process is running 
if (!file_exists($lock_dir)) { 
    // we suppress error messages as the cache file exists in 99,999% of all requests 
    $data = @include $cache_file; 
} 
// cache file not found 
if ($data === false) { 
    // get data from database 
    $data = mysqli_fetch_assoc(mysqli_query($link, "SELECT first, last FROM users WHERE id = 123")); 
    // write data to cache if no writing process is running (race condition safe) 
    // we suppress E_WARNING of mkdir() because it is possible in 0,001% of all requests that the dir already exists after calling file_exists() 
    if (!file_exists($lock_dir) && @mkdir($lock_dir)) { 
     file_put_contents($cache_file, '<?php return ' . var_export($data, true) . '; ?' . '>')) { 
     // remove lock 
     rmdir($lock_dir); 
    } 
} 

अब, हम flock() के साथ एक ही प्राप्त करने के लिए प्रयास करें:

$data = false; 
$cache_file = 'cache/first_last123.inc'; 
// we suppress error messages as the cache file exists in 99,999% of all requests 
$fp = @fopen($cache_file, "r"); 
// read data from cache if no writing process is running 
if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) { 
    // we suppress error messages as the cache file exists in 99,999% of all requests 
    $data = @include $cache_file; 
    flock($fp, LOCK_UN); 
} 
// cache file not found 
if (!is_array($data)) { 
    // get data from database 
    $data = mysqli_fetch_assoc(mysqli_query($link, "SELECT first, last FROM users WHERE id = 123")); 
    // write data to cache if no writing process is running (race condition safe) 
    $fp = fopen($cache_file, "c"); 
    if (flock($fp, LOCK_EX | LOCK_NB)) { 
     ftruncate($fp, 0); 
     fwrite($fp, '<?php return ' . var_export($data, true) . '; ?' . '>'); 
     flock($fp, LOCK_UN); 
    } 
} 

महत्वपूर्ण हिस्सा LOCK_NB सभी को अवरुद्ध से बचने के लिए है लगातार अनुरोध:

यह भी ऊपर संचालन से एक के लिए बिटमास्क रूप LOCK_NB जोड़ना संभव है यदि आप नहीं है झुंड() क ब्लॉक करना चाहते हैं आईइल लॉकिंग।

इसके बिना, कोड एक बड़ी बाधा उत्पन्न करेगा!

एक अतिरिक्त महत्वपूर्ण हिस्सा if (!is_array($data)) { है।इसका कारण यह है $ डेटा शामिल हो सकते हैं:

  1. डाटाबेस क्वेरी
  2. में नाकाम रहने के include
  3. या कोई रिक्त स्ट्रिंग (race condition)

रेस स्थिति होता है की false के परिणामस्वरूप array() यदि पहला विज़िटर इस लाइन को निष्पादित करता है:

$fp = fopen($cache_file, "c"); 

और एक अन्य आगंतुक एक मिलीसेकंड बाद में इस लाइन को निष्पादित करता है:

if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) { 

यह पहली आगंतुक खाली फ़ाइल बनाता है इसका मतलब है, लेकिन दूसरी आगंतुक ताला बनाता है और इसलिए include कोई रिक्त स्ट्रिंग देता है।

$filename = 'index.html'; 
$loops = 10000; 
$start = microtime(true); 
for ($i = 0; $i < $loops; $i++) { 
    file_exists($filename); 
} 
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL; 
$start = microtime(true); 
for ($i = 0; $i < $loops; $i++) { 
    $fp = @fopen($filename, "r"); 
    flock($fp, LOCK_EX | LOCK_NB); 
} 
echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL; 

परिणाम:

file_exists: 0.00949 
fopen/flock: 0.06401 

पी.एस.

तो तुम, तेजी से mkdir() और उसके 7x का उपयोग कर भी के माध्यम से कई नुकसान है कि बचा जा सकता है देखा जैसा कि आप देख सकते हैं कि mkdir() के सामने मैं file_exists() का उपयोग करता हूं। ऐसा इसलिए है क्योंकि my tests (जर्मन) ने अकेले mkdir() का उपयोग करके बाधा उत्पन्न की।

+1

"अगर आप फ़ाइल लॉक करते हैं और आपकी स्क्रिप्ट लिखते समय रुक जाती है लॉक जारी होने वाली फ़ाइल में। " - यदि आप लॉक का उपयोग कर नियंत्रण प्रक्रियाओं को नियंत्रित करने के लिए उपयोग कर रहे हैं, तो यह एक बहुत ही वांछनीय विशेषता है। एक प्रक्रिया के मरने के बाद चारों ओर लटकने वाले ताले के बारे में चिंता न करें, सरल प्रसंस्करण के लिए बनाता है। हमेशा के रूप में, आप अंततः प्राप्त करने की कोशिश कर रहे हैं पर निर्भर करता है। – Jason

+1

वहां [@ ऑपरेटर] (http://php.net/manual/en/language.operators.errorcontrol.php) का बहुत अधिक उपयोग है, यह सभी प्रकार के तरीकों से उड़ा सकता है और आप कभी नहीं पता है क्या हुआ। –

+0

@ जोसिप्रोडिन मैं अपना जवाब दोबारा लिखता हूं। मैं अभी भी @ ऑपरेटर का उपयोग करता हूं लेकिन स्पष्टीकरण जोड़ता हूं। – mgutt

2

आप mkdir पर आधारित अपने पढ़ने/लिखने के संचालन के आस-पास एक अनलॉक पैटर्न को अनलॉक कर सकते हैं, क्योंकि यह परमाणु और बहुत तेज़ है। मैंने तनाव का परीक्षण किया है और मैगुट के विपरीत एक बाधा नहीं मिली है। हालांकि, आपको डेडलॉक परिस्थितियों का ख्याल रखना होगा, जो संभवतया मगत्त का अनुभव करता है। एक मृत ताला तब होता है जब दो लॉक प्रयास एक दूसरे पर इंतजार करते रहते हैं। इसे लॉक प्रयासों पर यादृच्छिक अंतराल से उपचार किया जा सकता है। इसलिए जैसा:

// call this always before reading or writing to your filepath in concurrent situations 
function lockFile($filepath){ 
    clearstatcache(); 
    $lockname=$filepath.".lock"; 
    // if the lock already exists, get its age: 
    [email protected]($lockname); 
    // attempt to lock, this is the really important atomic action: 
    while ([email protected]($lockname)){ 
     if ($life) 
      if ((time()-$life)>120){ 
       //release old locks 
       rmdir($lockname); 
       $life=false; 
     } 
     usleep(rand(50000,200000));//wait random time before trying again 
    } 
} 

फिर filepath में अपनी फ़ाइल पर काम करते हैं और यह करने के बाद, फोन:

function unlockFile($filepath){ 
    $unlockname= $filepath.".lock"; 
    return @rmdir($unlockname); 
} 

मैं चुन लिया है अच्छी तरह से करने के बाद में अधिकतम पीएचपी निष्पादन समय, पुराने ताले को दूर करने के अगर अनलॉक होने से पहले एक स्क्रिप्ट निकलती है। स्क्रिप्ट विफल होने पर ताले को हमेशा हटाने का एक बेहतर तरीका होगा। इसके लिए एक साफ तरीका है, लेकिन मैं भूल गया हूँ।

0

मैं इस प्रश्न की सराहना करता हूं कि कुछ साल पुराना है, लेकिन मुझे लगता है कि झुंड के लिए एक कामकाजी उदाहरण/प्रतिस्थापन भवन के लायक हो सकता है। मैंने इसे अन्य उत्तरों पर आधारित किया है, लेकिन किसी ऐसे व्यक्ति के लिए जो पूरी तरह से झुंड की कार्यक्षमता को प्रतिस्थापित करने के लिए देख रहा है (एक ही समय में एक फ़ाइल लिखने के बजाय (हालांकि यह PHP मैनुअल झुंड उदाहरण को प्रतिबिंबित करता है)) मेरा मानना ​​है कि निम्नलिखित

पर्याप्त होंगे
function my_flock ($path,$release = false){ 
    if ($release){ 
     @rmdir($path); 
    } else { 
     return !file_exists($path) && @mkdir($path); 
    } 
} 
संबंधित मुद्दे