9.5 信号量内核对象(Semaphore)

(1)信号量的组成

  ①计数器:该内核对象被使用的次数

  ②最大资源数量:标识信号量可以控制的最大资源数量(带符号的32位)

  ③当前资源数量:标识当前可用资源的数量(带符号的32位)。即表示当前开放资源的个数(注意不是剩下资源的个数),只有开放的资源才能被线程所申请。但这些开放的资源不一定被线程占用完。比如,当前开放5个资源,而只有3个线程申请,则还有2个资源可被申请,但如果这时总共是7个线程要使用信号量,显然开放的资源5个是不够的。这时还可以再开放2个,直到达到最大资源数量。

(2)信号量的使用规则

  ①如果当前资源计数>0,那么信号量处于触发状态,表示有可用资源

  ②如果当前资源计数=0,那么信号量处于未触发状态,表示没有可用资源

  ③系统绝不会让当前资源计数变为负数;

  ④当前资源计数绝对不会大于最大资源计数

(3)信号量的用法

(4)相关函数

  ①创建信号量CreateSemaphore

参数

描述

psa

安全属性

lInitialCount

初始化时,共有多少个资源是可用的。如果该值设为0,表示没有可用资源,此时信号量为未触发状态,任何等待信号数的线程将进入等待状态。以后可通过调用ReleaseSemaphore来增加可用资源,同时变为触发状态。

LMaximumCount

能够处理的最大的资源数量

pszName

信号量的名称

  ②增加信号量:ReleaseSemaphore

参数

描述

hSemaphore

信号量句柄

lReleaseCount

将lReleaseCount值加到信号量的当前资源计数上

pLPreviousCount

返回当前资源计数的原始值,一般填NULL

  ★调用ReleaseSemaphore可以获得资源计数的原始值,但同时会增加当前资源的计数值。目前还没有办法在不改变当前资源计数值的前提下获得信号量的可用资源数。(即使lReleaseCount填入0也获取不到!)

  ③Wait*之类的等待函数:当线程调用等待函数时,如果信号量处于触发状态(可用资源大于0),则线程获得信号量的一个资源并把可用资源数量减1,同时继续执行。如果信号量处于未触发状态,则线程进入等待状态。直到其他线程释放资源。

【Semaphore程序】

#include <windows.h>
#include <tchar.h>
#include <locale.h> //////////////////////////////////////////////////////////////////////////
//一个只能容纳10个客人的餐馆来了12个客户
#define MAX_SEM_COUNT 10
#define THREADCOUNT 12 //////////////////////////////////////////////////////////////////////////
HANDLE g_hSemaphore = NULL;
DWORD WINAPI ThreadProc(LPVOID pvParam); //////////////////////////////////////////////////////////////////////////
int _tmain()
{ _tsetlocale(LC_ALL, _T("chs")); HANDLE aThread[THREADCOUNT] = { NULL };
DWORD dwThreadID = ; g_hSemaphore = CreateSemaphore(NULL, MAX_SEM_COUNT, MAX_SEM_COUNT, NULL);
//以下两句与上面那句的实现的效果一样,先创建无资源的信号量,再增加资源
//g_hSemaphore = CreateSemaphore(NULL, 0, MAX_SEM_COUNT, NULL);
//ReleaseSemaphore(g_hSemaphore, MAX_SEM_COUNT, NULL); if (NULL == g_hSemaphore){
_tprintf(_T("创建信号量失败:%d\n"), GetLastError());
return -;
} //创建12个线程
for (int i = ; i < THREADCOUNT;i++){
aThread[i] = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)ThreadProc,
NULL,,&dwThreadID);
if (aThread[i] == NULL){
_tprintf(_T("创建线程失败:%d\n"), GetLastError());
return -;
}
} //等待所有线程结束
WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE); //关闭所有子线程
for (int i = ; i < THREADCOUNT;i++){
CloseHandle(aThread[i]);
} //关闭信号量
CloseHandle(g_hSemaphore); _tsystem(_T("PAUSE"));
return ;
} //线程函数
DWORD WINAPI ThreadProc(LPVOID pvParam)
{
DWORD dwWaitResult;
BOOL bContinue = TRUE;
LONG lPrevCount=; while (bContinue){
//等待资源,立即返回
dwWaitResult = WaitForSingleObject(g_hSemaphore, 0L);
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
bContinue = FALSE;
_tprintf(_T("线程%d:等待成功!\n"), GetCurrentThreadId()); Sleep(); if (!ReleaseSemaphore(g_hSemaphore, , &lPrevCount)){
_tprintf(_T("ReleaseSemaphore失败:%d!\n"), GetLastError());
} //_tprintf(_T("线程%d:等待成功,当前可用资源%d!\n"), GetCurrentThreadId(),lPrevCount+1); break;
case WAIT_TIMEOUT:
_tprintf(_T("线程%d:等待超时!\n"), GetCurrentThreadId());
break;
}
}
_tprintf(_T("线程%d:退出\n"), GetCurrentThreadId());
return ;
}

9.6 互斥量内核对象(Mutex)

(1)互斥量的组成

  ①使用计数:与其他内核对象,都有一个使用计数

  ②线程ID:标识当前占用这个互斥量是哪个线程

  ③递归计数:该线程占用互斥量的次数

(2)互斥量的规则

  ①如果线程ID为0(即无效ID),那么该互斥量不为任何线程所占,即处于触发状态

  ②如果线程ID为非零值,表示该线程己经占用该互斥量,它处于非触发状态。

  ③其他内核对象只会记录哪些线程正在等待,但互斥量还会记录哪个线程等待成功。这使得它在未被触发的时候,也可以被线程所获得。当任何线程试图调用ReleasMutex时,该函数会检查调用线程的ID与互斥量内部保存的ID是否一致。如果一致,则递归计数递减。否则返回FALSE,这时调用GetLastError将得到ERROR_NOT_OWNER。

(3)互斥量的使用方法

  ①创建互斥量:CreateMutext(psa,bInitialOwner,pszName);其中bInitialOwner为TRUE时表示将调用线程ID设为互斥量内部的线程ID,递归计数加1。否则表示互斥量不被占用(处于触发状态)

  ②释放互斥量:ReleaseMutex,递归计数减1,当递归计数为0时,将线程ID设为0。这里互斥量被触发。

9.6.1 互斥量被“遗弃问题”

如果占用互斥量的线程在释放互斥量之前提前终止,这种现象叫互斥量被“遗弃”(Abandoned)系统会自动将该互斥量的线程ID和递归计数设为0。再“公平地”唤醒正在等待的一个线程,但这时唤醒的线程从Wait*中返回的不再是WAIT_OBJECT_0,而是返回WAIT_ABANDONED,以表示该线程等到了一个被遗弃的互斥量。所以此时被互斥量保护的资源处于什么状态,这个被唤醒的线程并不知道,要求应用程序自己决定怎么做。

9.6.2 互斥量与关键段的比较

特征

互斥量

关键段

性能

是否能跨进程使用

声时

HANDLE hmtx

CRITICAL_SECTION cs;

初始化

CreateMutex

InitializeCriticalSection

清理

CloseHandle

DeleteCriticalSection

无限等待

Wait*(hmtx,INFINITE)

EnterCriticalSection(&cs)

0等待

Wait*(hmtx,0)

TryEnterCriticalSection

任意时长等待

Wait*(htmx,dwMilliseconds)

不支持

释放

ReleaseMutex

LeaveCriticalSection

是否能同时等待其他内核对象

如WaitForMultipleObject

【Queue程序】演示生产消费问题(多线程安全队列版)

//Queue.h

#pragma  once

#include <windows.h>
//队列类(线程安全)
class CQueue{
public:
struct ELEMENT{
int m_nThreadNum; //客户线程号
int m_nRequestNum; //客户请求号
//其他字段应该这之后
};
typedef ELEMENT* PELEMENT;
private:
PELEMENT m_pElements; //要处理的队列元素的数组,也是需要被保护的数据
int m_nMaxElements; //数组元素最大数量
HANDLE m_h[]; //互斥量和信号量
HANDLE &m_hmtxQ; //m_h[0]的引用,用来决定是否可以访问队列
HANDLE &m_hsemNumElements; //m_h[1]的引用,用来判断队列是否有可用的空间 public:
CQueue(int nMaxElements);
~CQueue();
BOOL Append(PELEMENT pElement, DWORD dwMilliseconds);//增加一个元素
BOOL Remove(PELEMENT pElement, DWORD dwMilliseconds); //从队列取出一个元素
};

//Queue.cpp

#include "Queue.h"

//////////////////////////////////////////////////////////////////////////
CQueue::CQueue(int nMaxElements) :m_hmtxQ(m_h[]), m_hsemNumElements(m_h[])
{
m_pElements = (PELEMENT)HeapAlloc(GetProcessHeap(), , sizeof(ELEMENT)*nMaxElements);
m_nMaxElements = nMaxElements; //创建互斥量和信号量
m_hmtxQ = CreateMutex(NULL, FALSE, NULL);
m_hsemNumElements = CreateSemaphore(NULL, , nMaxElements, NULL);
} //////////////////////////////////////////////////////////////////////////
CQueue::~CQueue()
{
CloseHandle(m_hsemNumElements);
CloseHandle(m_hmtxQ);
HeapFree(GetProcessHeap(), , m_pElements);
} //////////////////////////////////////////////////////////////////////////
BOOL CQueue::Append(PELEMENT pElement, DWORD dwMillseconds)
{
BOOL fOk = FALSE; //以独占方式访问队列
DWORD dw = WaitForSingleObject(m_hmtxQ, dwMillseconds); if (dw == WAIT_OBJECT_0){ //判断队列是否己满(即信号量中是否还有可用资源)
//因为增加可用资源的数量(每次增加1),直到被用完(m_nMaxElements)
//所以lPrevCount返回上次可用资源的数量
LONG lPrevCount;
fOk = ReleaseSemaphore(m_hsemNumElements, , &lPrevCount); if (fOk){ //增加1个资源,如果成功说明资源还没用完,仍有可用资源
//队列未满,则增加一个新元素
m_pElements[lPrevCount] = *pElement;
} else{
//队列己满,设置出错代码,并返回FALSE
SetLastError(ERROR_DATABASE_FULL);
}
//释放互斥锁,允许其他线程访问队列
ReleaseMutex(m_hmtxQ);
} else{
//超时,设置出错代码并返回FALSE
SetLastError(ERROR_TIMEOUT);
} return fOk;
} //////////////////////////////////////////////////////////////////////////
//读取(并删除)队列中的元素
BOOL CQueue::Remove(PELEMENT pElement, DWORD dwMillseconds)
{
//要读取,首先要确保对队的独占访问权,所以要等待互斥锁。
//其次,队列要有元素,即信号量要有可用资源(即信号量是触发状态)
//只有当这两个条件都满足时,才会唤醒服务线程来读取元素。
BOOL fOk =(WAIT_OBJECT_0 == WaitForMultipleObjects(_countof(m_h),m_h,TRUE,dwMillseconds)); if (fOk){
//队列至少有一个元素,从队列中读取出来
*pElement = m_pElements[]; //先进先出 //将剩余的元素向前移
MoveMemory(&m_pElements[], &m_pElements[],
sizeof(ELEMENT)*(m_nMaxElements-)); //释放互斥锁,以便其他线程访问队列
ReleaseMutex(m_hmtxQ);
} else
{
//超时
SetLastError(ERROR_TIMEOUT);
} return fOk; //调用GetLassError来获得更多信息
}

//main.cpp

#include "../../CommonFiles/CmnHdr.h"
#include <windows.h>
#include "resource.h"
#include "Queue.h"
#include <tchar.h>
#include <strsafe.h> //////////////////////////////////////////////////////////////////////////
CQueue g_q(); //共享队列
volatile LONG g_bShutdown = FALSE; //标识是否结束客户或服务线程
HWND g_hwnd; //对话框句柄 //客户线程和服务线程
HANDLE g_hThreads[MAXIMUM_WAIT_OBJECTS]; //最多64个线程
int g_nNumThreads; //当前产生的线程总数 //////////////////////////////////////////////////////////////////////////
DWORD WINAPI ClientThread(PVOID pvParam)
{
int nThreadNum = PtrToUlong(pvParam);
HWND hwndLB = GetDlgItem(g_hwnd, IDC_CLIENTS); int nRequestNum = ;
TCHAR sz[];
//InterlockedCompareExchange比较参数1与3,如果相等,则
//则g_bShutdown=参数2,否则g_bShutdown不变。返回g_bShubdown
//的初始值。即如果g_bShutdown=false,则返回0,如果g_bShutdown=true
//则返回true
while ( != InterlockedCompareExchange(&g_bShutdown,,)){
//追踪当前线程的请求号
nRequestNum++; //每个线程的请求号,不断递增 CQueue::ELEMENT e = { nThreadNum, nRequestNum }; //尝试将元素放入队列中
if (g_q.Append(&e,)){
//
StringCchPrintf(sz, _countof(sz), TEXT("线程%d,发送请求%d"),
nThreadNum,nRequestNum);
} else
{
//不能将元素放入队列中
StringCchPrintf(sz, _countof(sz), TEXT("线程%d,发送请求%d失败(%s)"),
nThreadNum,nRequestNum,(GetLastError()==ERROR_TIMEOUT)?
TEXT("超时"):TEXT("队列己满"));
}
//显示增加结果
ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB,sz));
Sleep(); //在增加另一新元素时休眠2.5秒
} return ;
} //////////////////////////////////////////////////////////////////////////
DWORD WINAPI ServerThread(PVOID pvParam)
{
int nThreadNum = PtrToUlong(pvParam);
HWND hwndLB = GetDlgItem(g_hwnd, IDC_SERVERS);
TCHAR sz[]; while ( != InterlockedCompareExchange(&g_bShutdown, , )){
CQueue::ELEMENT e; //尝试从队列中获取一个元素
if (g_q.Remove(&e,)){
//标明是哪个服务线程处理队列中的元素
StringCchPrintf(sz, _countof(sz), TEXT("服务线程%d处理了客户%d的%d号请求!"),
nThreadNum, e.m_nThreadNum,e.m_nRequestNum); //让服务线程消费速度慢一点(否则可能会超过客户生产数据的速度)
Sleep(*e.m_nThreadNum);
} else{
//无法从队列中获取得元素
StringCchPrintf(sz, _countof(sz), TEXT("服务线程%d:(超时)"),nThreadNum);
}
//显示处理结果
ListBox_SetCurSel(hwndLB,ListBox_AddString(hwndLB,sz));
}
return ;
} //////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
chSETDLGICONS(hwnd, IDI_QUEUE);
g_hwnd = hwnd; DWORD dwThreadID; //创建客户线程
for (int x = ; x < ;x++)
g_hThreads[g_nNumThreads++] =
chBEGINTHREADEX(NULL, , ClientThread, (PVOID)(INT_PTR)x,
, &dwThreadID);
//创建服务线程
for (int x = ; x < ;x++){
g_hThreads[g_nNumThreads++] =
chBEGINTHREADEX(NULL, , ServerThread, (PVOID)(INT_PTR)x,
, &dwThreadID);
}
return TRUE;
} //////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity)
{
switch (id)
{
case IDCANCEL:
EndDialog(hwnd, id);
break;
}
} //////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
}
return FALSE;
} //////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int)
{
DialogBox(hInstExe, MAKEINTRESOURCE(IDD_QUEUE), NULL, Dlg_Proc); //退出客户和服务线程
InterlockedExchange(&g_bShutdown, TRUE); //等待所有线程结束
WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE);
while (g_nNumThreads--)
CloseHandle(g_hThreads[g_nNumThreads]); return ;
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 9_Queue.rc 使用
//
#define IDD_QUEUE 101
#define IDI_QUEUE 102
#define IDC_SERVERS 1001
#define IDC_CLIENTS 1002 // Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1004
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

//Queue.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h" #define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h" /////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS /////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED #ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
// TEXTINCLUDE
BEGIN
"resource.h\0"
END TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END #endif // APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////////
//
// Dialog
// IDD_QUEUE DIALOGEX , , ,
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "队列(多线程安全版)"
FONT , "MS Shell Dlg", , , 0x1
BEGIN
GROUPBOX "客户线程",IDC_STATIC,,,,
GROUPBOX "服务线程",IDC_STATIC,,,,
LISTBOX IDC_SERVERS,,,,,NOT LBS_NOTIFY | WS_VSCROLL | WS_TABSTOP
LISTBOX IDC_CLIENTS, , , , , NOT LBS_NOTIFY | WS_VSCROLL | WS_TABSTOP
END /////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
// #ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_QUEUE, DIALOG
BEGIN
LEFTMARGIN,
RIGHTMARGIN,
TOPMARGIN,
BOTTOMMARGIN,
END
END
#endif // APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////////
//
// Icon
// // Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_QUEUE ICON "Queue.ico"
#endif // 中文(简体,中国) resources
///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
// /////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

第9章 用内核对象进行线程同步(3)_信号量(semaphore)、互斥对象(mutex)的更多相关文章

  1. Windows核心编程:第9章 用内核对象进行线程同步

    Github https://github.com/gongluck/Windows-Core-Program.git //第9章 用内核对象进行线程同步.cpp: 定义应用程序的入口点. // #i ...

  2. windows核心编程---第八章 使用内核对象进行线程同步

    使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...

  3. C++之内核对象进行线程同步

    用户模式下的线程同步机制提供了非常好的性能,但他们也的确存在一些局限性,而且不适用于许多应用程序,例如,对Interlocked系列函数只能对一个值进行操作,它们从来不会把线程切换到等待状态.我们可以 ...

  4. 《Windows核心编程系列》八谈谈用内核对象进行线程同步

    使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...

  5. 操作系统中的进程同步与Window中利用内核对象进行线程同步的关系

    操作系统中为了解决进程间同步问题提出了用信号量机制,信号量可分为四种类型分别是互斥型信号量,记录型信号量,AND型信号量,信号量集. 互斥型信号量 互斥型信号量是资源数量为1的特殊的记录型信号量.表示 ...

  6. 第8章 用户模式下的线程同步(3)_Slim读写锁(SRWLock)

    8.5 Slim读/写锁(SRWLock)——轻量级的读写锁 (1)SRWLock锁的目的 ①允许读者线程同一时刻访问共享资源(因为不存在破坏数据的风险) ②写者线程应独占资源的访问权,任何其他线程( ...

  7. [7] Windows内核情景分析---线程同步

    基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ...

  8. [C++] socket - 6 [API互斥事件对象实现线程同步]

    /*API互斥事件对象实现线程同步*/ #include<windows.h> #include<stdio.h> DWORD WINAPI myfun1(LPVOID lpP ...

  9. [C++] socket - 5 [API事件对象实现线程同步]

    /*API事件对象实现线程同步*/ #include<windows.h> #include<stdio.h> DWORD WINAPI myfun1(LPVOID lpPar ...

  10. Windows核心编程:第8章 用户模式下的线程同步

    Github https://github.com/gongluck/Windows-Core-Program.git //第8章 用户模式下的线程同步.cpp: 定义应用程序的入口点. // #in ...

随机推荐

  1. 【iScroll源码学习01】准备阶段

    前言 我们昨天初步了解了为什么会出现iScroll:[SPA]移动站点APP化研究之上中下页面的iScroll化(上),然后简单的写了一个demo来模拟iScroll,其中了解到了以下知识点: ① v ...

  2. loading插件(原创)

    前言:服务器这几天都连不上,所以迟迟未更新,今天连上后才把插件文件和文档上传了.良心之作啊,难度虽不高,但命名多文件多啊.我得马上写篇博客絮叨一下,直接上地址. 文档及下载地址:www.chenggu ...

  3. ie8不兼容rgba的解决

    借鉴................. 在调试ie8兼容性的问题时,发现ie8不支持rgba. 关于rgba(),即为颜色设置的方法函数,rgb代表颜色,a代表透明度. 如rgba(0,0,0,0.1 ...

  4. CSS学习总结(二)

    一.id及class选择符 id和class的名称是由用户自定义的.id号可以唯一地标识html元素,为元素指定样式.id选择符以#来定义. 1.id选择符   注:在网页中,每个id名只能是唯一不重 ...

  5. DevExtreme官方视频教程分享

    收集在此,希望对使用这个工具的人有帮助 DevExtreme 1 2 3 4 5 6 DevExpress DevExtreme入门视频一:Getting Started DevExpress Dev ...

  6. Android Testing学习02 HelloTesting 项目建立与执行

    Android Testing学习02 HelloTesting 项目建立与执行 Android测试,分为待测试的项目和测试项目,这两个项目会生成两个独立的apk,但是内部,它们会共享同一个进程. 下 ...

  7. Service和Thread的关系及如何启用Service,如何停用Service

    Service和Thread的关系: 不少Android初学者都可能会有这样的疑惑,Service和Thread到底有什么关系呢?什么时候应该用Service,什么时候又应该用Thread?答案可能会 ...

  8. CoreGraphics相关方法

    // 将view转为image(不经常用到的功能)(摘自SCCatWaitingHUD) - (UIImage *)convertViewToImage { CGSize s = self.bound ...

  9. iOS中如何知道app版本已更新

    主要用于程序升级,开启程序后是否显示新特性两个方面. 1.苹果app版本 苹果规定,程序的版本只能升不能降.例如1.0->1.1可以,1.1->1.0就不可以,不允许上架. 2.app版本 ...

  10. HTTP协议基本知识

    Xcode7.0以上版本必须操作:https http 在Info.plist中添加NSAppTransportSecurity类型Dictionary. 在NSAppTransportSecurit ...