1. 程式人生 > >CAsyncSocket物件不能跨執行緒之分析 (轉載)

CAsyncSocket物件不能跨執行緒之分析 (轉載)

現象

用多執行緒方法設計socket程式時,你會發現在跨執行緒使用CAsyncSocket及其派生類時,會出現程式崩潰。所謂跨執行緒,是指該物件在一個執行緒中 呼叫Create/AttachHandle/Attach函式,然後在另外一個執行緒中呼叫其他成員函式。下面的例子就是一個典型的導致崩潰的過程:
CAsyncSocket Socket;
UINT Thread(LPVOID)
{
       Socket.Close ();
       return 0;
}
void CTestSDlg::OnOK() 
{
       // TODO: Add extra validation here
       Socket.Create(0);
       AfxBeginThread(Thread,0,0,0,0,0);
}

其中Socket物件在主執行緒中被呼叫,在子執行緒中被關閉。

跟蹤分析

這個問題的原因可以通過單步跟蹤(F11)的方法來了解。我們在Socket.Create(0)處設斷點,跟蹤進去會發現下面的函式被呼叫:

void PASCAL CAsyncSocket::AttachHandle(
          SOCKET hSocket, CAsyncSocket* pSocket, BOOL bDead)
{
    _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
    BOOL bEnable = AfxEnableMemoryTracking(FALSE);
    if (!bDead)
    {
             ASSERT(CAsyncSocket::LookupHandle(hSocket, bDead) == NULL);
             if (pState->m_pmapSocketHandle->IsEmpty())
             {
                  ASSERT(pState->m_pmapDeadSockets->IsEmpty());
                  ASSERT(pState->m_hSocketWindow == NULL);
                  CSocketWnd* pWnd = new CSocketWnd;
                  pWnd->m_hWnd = NULL;
                  if (!pWnd->CreateEx(0, AfxRegisterWndClass(0),
                                   _T("Socket Notification Sink"),
                                 WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL))
                 {
                       TRACE0("Warning: unable to create socket notify window!\n");
                       AfxThrowResourceException();
                 }
                 ASSERT(pWnd->m_hWnd != NULL);
                 ASSERT(CWnd::FromHandlePermanent(pWnd->m_hWnd) == pWnd);
                 pState->m_hSocketWindow = pWnd->m_hWnd;
            }
            pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);
    }
    else
    {
           int nCount;
           if (pState->m_pmapDeadSockets->Lookup((void*)hSocket, (void*&)nCount))
                     nCount++;
           else
                     nCount = 1;
           pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);
   }
   AfxEnableMemoryTracking(bEnable);
}

在這個函式的開頭,首先獲得了一個pState的指標指向_afxSockThreadState物件。從名字可以看出,這似乎是一個和執行緒相關的變數,實際上它是一個巨集,定義如下:

#define _afxSockThreadState AfxGetModuleThreadState()

我們沒有必要去細究這個指標的定義是如何的,只要知道它是和當前執行緒密切關聯的,其他執行緒應該也有類似的指標,只是指向不同的結構。

在這個函式中,CAsyncSocket建立了一個視窗,並把如下兩個資訊加入到pState所管理的結構中:

    pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);
    pState->m_pmapDeadSockets->SetAt((void*)hSocket, (void*)nCount);
    pState->m_hSocketWindow = pWnd->m_hWnd;
    pState->m_pmapSocketHandle->SetAt((void*)hSocket, pSocket);

當呼叫Close時,我們再次跟蹤,就會發現在KillSocket中,下面的函數出現錯誤:

    void PASCAL CAsyncSocket::KillSocket(SOCKET hSocket, CAsyncSocket* pSocket)
    {
            ASSERT(CAsyncSocket::LookupHandle(hSocket, FALSE) != NULL);

我們在這個ASSERT處設定斷點,跟蹤進LookupHandle,會發現這個函式定義如下:

CAsyncSocket* PASCAL CAsyncSocket::LookupHandle(SOCKET hSocket, BOOL bDead)
{
     CAsyncSocket* pSocket;
     _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
     if (!bDead)
     {
             pSocket = (CAsyncSocket*)
             pState->m_pmapSocketHandle->GetValueAt((void*)hSocket);
             if (pSocket != NULL)
                  return pSocket;
    }
    else
    {
             pSocket = (CAsyncSocket*)
                  pState->m_pmapDeadSockets->GetValueAt((void*)hSocket);
             if (pSocket != NULL)
                   return pSocket;
    }
    return NULL;
}

顯然,這個函式試圖從當前執行緒查詢關於這個 socket的資訊,可是這個資訊放在建立這個socket的執行緒中,因此這種查詢顯然會失敗,最終返回NULL。

有人會問,既然它是ASSERT出錯,是不是Release就沒問題了。這只是自欺欺人。ASSERT/VERIFY都是檢驗一些程式正常執行必須正確的條件。如果ASSERT都失敗,在Release中也許不會顯現,但是你的程式肯定執行不正確,啥時候出錯就不知道了。

如何在多執行緒之間傳遞socket

有些特殊情況下,可能需要在不同執行緒之間傳遞socket。當然我不建議在使用CAsyncSOcket的時候這麼做,因為這增加了出錯的風險(尤其當出現拆解包問題時,有人稱為粘包,我基本不認同這種稱呼)。如果一定要這麼做,方法應該是:

  1. 當前擁有這個socket的執行緒呼叫Detach方法,這樣socket控制代碼和C++物件及當前執行緒脫離關係
  2. 當前執行緒把這個物件傳遞給另外一個執行緒
  3. 另外一個執行緒建立新的CAsyncSocket物件,並呼叫Attach

上面的例子,我稍微做修改,就不會出錯了:

CAsyncSocket Socket;
UINT Thread(LPVOID sock)
{
         Socket.Attach((SOCKET)sock);
         Socket.Close ();
         return 0;
}
void CTestSDlg::OnOK() 
{
         // TODO: Add extra validation here
         Socket.Create(0);
         SOCKET hSocket = Socket.Detach ();
         AfxBeginThread(Thread,(LPVOID)hSocket,0,0,0,0);
}

相關推薦

CAsyncSocket物件不能執行分析 (轉載)

現象 用多執行緒方法設計socket程式時,你會發現在跨執行緒使用CAsyncSocket及其派生類時,會出現程式崩潰。所謂跨執行緒,是指該物件在一個執行緒中 呼叫Create/AttachHandle/Attach函式,然後在另外一個執行緒中呼叫其他成員函式。下面的例子

執行委託執行問題分析--在建立視窗控制代碼之前,不能在控制元件上呼叫 Invoke 或 BeginInvoke(解決方法已更新)

檢視巢狀檢視+groupby+sum+if超慢?檢視巢狀檢視+groupby+sum+if超慢? 炯蕉蔚郝iar貉k湯秤TP2Fx扯訃詬壤撞蝸 《 http://babyknow.baidu.com/article/1376a5480527629546e457877078

從零開始學多執行共享物件(二)

想要使用多執行緒程式設計,有一個很重要的前提,那就是必須保證操縱的是執行緒安全的類. 那麼如何構建執行緒安全的類呢? 1. 使用同步來避免多個執行緒在同一時間訪問同一資料. 2. 正確的共享和安全的釋出物件,使多個執行緒能夠安全的訪問它們. 那麼如何正確的共享和安全的釋出物件呢? 這正是這篇部落格要告訴你的.

從0開始學多執行共享物件(二)

想要使用多執行緒程式設計,有一個很重要的前提,那就是必須保證操縱的是執行緒安全的類. 那麼如何構建執行緒安全的類呢? 1. 使用同步來避免多個執行緒在同一時間訪問同一資料. 2. 正確的共享和安全的釋出物件,使多個執行緒能夠安全的訪問它們. 那麼如何正確的共享和安全的釋出物件呢? 這正是這篇部落格要告訴你的.

從零開始學多執行組合物件(三)

前文回顧 通過博主之前釋出的兩篇部落格從零開始學多執行緒之執行緒安全(一)和從零開始學多執行緒之共享物件(二)講解的知識點,我們現在已經可以構建執行緒安全的類了,本篇將為您介紹構建類的模式,這些模式讓類更容易成為執行緒安全的,並且不會讓程式意外破壞這些類的執行緒安全性. 本篇部落格將要講解的知識點 構建執行

菜鳥成長Thread的SetDeamon()守護執行原始碼分析(4)

    相信大家的童年都有過《西遊記》的陪伴,唐僧四人一起去西天取經的故事肯定也是耳熟能詳,在西遊記裡唐僧作為整個取經隊伍的領導者,而徒弟們跟隨師傅指引的方向去前進,這裡我們可以把取經當成是Thread執行的終點,師傅作為被守護執行緒,徒弟作為守護執行緒,當取完經後或者師

Java多執行AQS(AbstractQueuedSynchronizer )實現原理和原始碼分析(三)

章節概覽、 1、回顧 上一章節,我們分析了ReentrantLock的原始碼: 2、AQS 佇列同步器概述 本章節我們深入分析下AQS(AbstractQueuedSynchronizer)佇列同步器原始碼,AQS是用來構建鎖或者其他同步元件的基礎框架。

Java多執行Condition實現原理和原始碼分析(四)

章節概覽、 1、概述 上面的幾個章節我們基於lock(),unlock()方法為入口,深入分析了獨佔鎖的獲取和釋放。這個章節我們在此基礎上,進一步分析AQS是如何實現await,signal功能。其功能上和synchronize的wait,notify一樣。

Android訊息機制原理,仿寫Handler Looper原始碼解析執行通訊原理--仿寫模擬Handler(四)

前篇總結:上一篇實現了用Looper管理訊息佇列和訊息迴圈,但是訊息的傳送和訊息處理都是在Looper中進行的。寫一個子執行緒使用這樣的Looper怎麼才能獲取到loop()死迴圈訊息佇列取出的訊息呢?用回撥!callBack! 第四節 仿寫Handler來發送訊息,實現回

Java問題定位Java執行堆疊分析

"main" prio=1 tid=0x0805c988 nid=0xd28 runnable [0xfff65000..0xfff659c8] at java.lang.String.indexOf(String.java:1352) at java.io.PrintStream.write

執行Callable介面及FutureTask原始碼分析

一、Callable和Future 對比Callable和Runnable: Runnable介面: public interface Runnable { public abstract void run(); } Callable介面: public int

Java多執行執行池深入分析

執行緒池是併發包裡面很重要的一部分,在實際情況中也是使用很多的一個重要元件。 下圖描述的是執行緒池API的一部分。廣義上的完整執行緒池可能還包括Thread/Runnable、Timer/TimerTask等部分。這裡只介紹主要的和高階的API以及架構和原理。 大

Android訊息機制原理,仿寫Handler Looper原始碼執行通訊原理--執行間通訊原理(一)

前言:我們都知道Android的執行緒通訊是用Handler、Looper機制實現的,面試也經常問道,網上也有很多文章介紹原始碼但是可能很多小白只是機械是的記憶,回答不清楚原理究竟是怎麼回事。下邊我將一步一步仿寫一個Handler、Looper模擬Android的執行緒間通訊

java多執行物件的併發訪問

1.synchronized同步方法 --1.1方法內的變數是執行緒安全的 解釋:由於方法內的變數是私有的,本體訪問的同時別人訪問不了,所以是執行緒安全的。 --1.2例項變數是非執行緒安全的 解釋:

JAVA多執行Synchronized關鍵字--物件鎖的特點

一,介紹 本文介紹JAVA多執行緒中的synchronized關鍵字作為物件鎖的一些知識點。 所謂物件鎖,就是就是synchronized 給某個物件 加鎖。關於 物件鎖 可參考:這篇文章 二,分析 synchronized可以修飾例項方法,如下形式: 1 public class My

Java多執行BlockingQueue深入分析

一、概述: BlockingQueue作為執行緒容器,可以為執行緒同步提供有力的保障。 二、BlockingQueue定義的常用方法 1.BlockingQueue定義的常用方法如下:   丟擲異常 特殊值 阻塞 超時 插入 add(e) offer(e) put(e) offer(e, time, un

Java執行如何分析死鎖及避免死鎖

什麼是死鎖 java中的死鎖是一種程式設計情況,其中兩個或多個執行緒被永久阻塞,Java死鎖情況出現至少兩個執行緒和兩個或更多資源。 在這裡,我們將寫了一個簡單的程式,它將導致java死鎖場景,然後我們將分析它。 怎麼實現死鎖 下面我們一起看一個簡單的死鎖事例

Java多執行物件及變數的併發訪問

Java物件及變數的併發訪問 當多個執行緒同時對同一個物件中的例項變數進行併發訪問時可能會產生執行緒安全問題。產生的後果就是”髒讀”,即收到的資料其實是被更改過的。 如果訪問的是方法中的變數,則不存在”非執行緒安全”問題 可以通過以下幾種方式來解決,在對物

Java多執行ThreadPoolExecutor實現原理和原始碼分析(五)

章節概覽、 1、概述 執行緒池的顧名思義,就是執行緒的一個集合。需要用到執行緒,從集合裡面取出即可。這樣設計主要的作用是優化執行緒的建立和銷燬而造成的資源浪費的情況。Java中的執行緒池的實現主要是JUC下面的ThreadPoolExecutor類完成的。下面

Java多執行多個執行訪問共享物件和資料的方式

1.如果每個執行緒執行的程式碼相同,可以使用同一個Runable物件,這個Runable物件中有那個共享資料,例如賣票系統就可以這樣做。 package javaplay.test; public class MulteThreadShareData { publi