2、多线程及多线程间同步执行
来源:互联网 发布:合肥晨飞网络待遇 编辑:程序博客网 时间:2024/05/22 04:55
多线程
多线程概念:一个程序中,程序把 "自己" 分成几个 "执行线程" 并行运行
在代码角度:一个程序首先执行主线程(WinMain),程序一旦执行,会进行一个系统调用(CreateThread),创建新的线程。Window 会在这些线程中作抢占式切换(就像在进程中切换一样)
线程与消息队列的关系:每个线程都可以创建或不创建消息队列,如果想在线程中创建窗口,则要创建消息队列。如果要做数据运算或图形理,则不用创建消息队列。因此,要学会把程序分成一个消息队列线程(该线程用于创建所有窗口并给他们发送消息)和一个或多个非消息队列线程(用于数据运算或后台任务)。
例如:
主线程创建所有的窗口,窗口过程,处理窗口的消息。其他线程负责简单的后台任务、数据运算,除了通过和主线程通信外,不和用户打道。
多线程需要注意的地方
由于一个程序里面的线程是以抢占式运行,线程共享同一个进程的变量和内存。当多个线程公用同一个数据时,Window 在一个线程更新并使用该数据前中断线程,启动另一个线程使用该数据,则这两个线程所 “看到” 的数据可能不一致,引起程序崩毁(Window 是在机器指令之间切换代码)
例如:
假设一个多线程的程序,线程A负责准备数据,线程B需要使用这些数据。如果线程B在线程A还没开始运行时就已经开始运行,则会出现常,因此,为了让线程之间协同运行,Window 提供各种方式如:
1、“信号灯”(Semaphore)让程序在代码的特定点暂停一个线程,直到另一个线程发信号让它继续(就像交通灯指挥交通一样),但 “信号灯”又会造成一种常见的问题: “死锁” ,即两个线程已经中断了彼此的运行,只有它们继续运行才能解开对方中断的代码(就像过独木桥,双方都在桥上两头,两个人都过不去)
2、“临界区”(Critical Section)指定该代码段不能被中断。临界区通过调用 EnterCriticalSection 和 LeaveCriticalSection 来定义,一个线程进入临界区,另一个线程就不能进入(必须等我过了,你才能通过),直到前一个线程调用 LeaveCriticalSection
注意事项:
尽量不要在线程中共享 GDI 对象(画笔、画刷、字体、位图。。。),这样有可能一个线程销毁 GDI 对象,另一个线程调用该对象,就算需要共享同一个 GDI 对象,也最好放在临界区
使用临界区
使用临街区共四个函数
1、定义一个类型为 CRITRICAL_SECTION cs 的全局变量
CRITRICAL_SECTION cs;
2、该变量在一个线程中进行如下初始化
InitializeCriticalSrction(&cs);(该变量不能被修改,移动)
3、变量被初始化后,在该线程中调用函数进入临界区
EnterCriticalSection(&cs);(调用成功后,该线程就进入临界区,如果下一个线程要进入同一个临界区,调用 EnterCriticalSection 进入同一个临界区时将被挂起)
4、多个线程进入同一个临界区时,第一个线程运行完后调用该函数,推出临界区,使 Window 执行下个线程
LeaveCriticalSection(&cs);(第一个线程调用该函数,离开临界区,下一个线程才能执行)
这里有两个线程函数分别是 ThreadPrco1 和 ThreadProc2,ThreadProc1 负责打印数字 1,ThreadProc2 负责打印数字 2,在不使用 “临界区” 的情况下,效果如下:
int flag = 1;void ThreadProc1(void *p){HDC hdc;static int Lines = 0;while(Lines < 20){hdc = GetDC(hwnd);TextOut(hdc, 10, Lines * ychar, TEXT("1"), 1);Lines++;ReleaseDC(hwnd, hdc);}_endthread();}void ThreadProc2(void *p){HDC hdc;static int iLines = 0;while(iLines < 20){hdc = GetDC(hwnd);TextOut(hdc, 60, iLines * ychar, TEXT("2"), 1);iLines++;ReleaseDC(hwnd, hdc);}_endthread();}
可以看出线程打印出来的数字基本是同时进行的
这里也是 ThreadProc1 打印数字 1,ThreadProc2 打印数字 2,但使用了 “临界区”
CRITICAL_SECTION cs;int flag = 1;void ThreadProc1(void *p){HDC hdc;static int Lines = 0;InitializeCriticalSection(&cs);EnterCriticalSection(&cs);while(Lines < 20){hdc = GetDC(hwnd);TextOut(hdc, 10, Lines * ychar, TEXT("1"), 1);Lines++;ReleaseDC(hwnd, hdc);}LeaveCriticalSection(&cs);_endthread();}void ThreadProc2(void *p){HDC hdc;static int iLines = 0;EnterCriticalSection(&cs);while(iLines < 20){hdc = GetDC(hwnd);TextOut(hdc, 60, iLines * ychar, TEXT("2"), 1);iLines++;ReleaseDC(hwnd, hdc);}_endthread();}
这里是先运行 ThreadProc1 把所有的 1 打印出来,再运行 ThreadProc2 线程函数
关于临界区的一个问题:
当多个(多于一个)线程进入同一个临界区时,线程的执行顺序时怎样的?
创建多线程
事件对象
如果一个程序每次运行相同的任务都为该任务重新创建新的线程,那么将有大量无谓的开销(该开销来自线程的创建和销毁)。那么有没有一种方法让二级线程完成一个任务后,停止运行(而不是销毁线程),等主线程再次发送信号的时候,二级线程再次运行呢?答案肯定是有,那就是事件对象
// 定义一个事件对象HANDLE hEvent;
<pre name="code" class="cpp">void ThreadProc(void *p){BOOL *ThreadClose = (BOOL *)p;while(1){// 该变量用于判断是否关闭线程if(ThreadClose)_endthread();
// 直到事件对象被激活,函数才返回 WaitForSingleObject(hEvent, INFINITE);// 函数主体}}LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ // 省略通常的变量定义// 定义一个变量,用于关闭线程BOOL ThreadClose = 0; switch(message) { case WM_CREATE:// 在这里创建一个事件对象,初始化事件对象为未激活状态hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
// 创建线程_beginthread(ThreadProc, 0, (void *)&ThreadClose); return 0;case WM_LBUTTONDOWN:
// 当鼠标左键时,激活事件对象SetEvent(hEvent);return 0;case WM_RBUTTONDOWN:
// 当鼠标右键时,事件对象回未激活状态ResetEvent(hEvent);return 0; case WM_DESTROY:
// 销毁事件对象DeleteObject(hEvent); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam);}
程序定义一个全局变量对象 hEvent ,调用 CreateEvent() 函数创建事件对象(第三个参数初始化对象状态为未激活状态)。当鼠标左键点击时,激活事件对象。当鼠标右击时,把事件对象设置为未激活对象。
WaitForSingleObject( HANDLE hEvent, LONG dwtime) 函数:
第二个参数:设置数值时,表示该函数等待指定的时间才返回(以毫秒为单位,但如果事件对象为激活状态,该函数立即返回)。设置为 INFINITE 时,表示函数等待事件对象激活为止。
CreateEvent( &sa, fManual, fInitial, pszName) 函数:
该函数返回一个事件对象
第一个参数:指向 SECURITY_ATTRIBUTES 结构的指针(只有事件对象在进程间共享时才有意义)
第二个参数:设置为 FALSE时,每次事件对象被激活后都自动设置为未激活状态
第三个参数:初始化事件对象的状态,为 TRUE 时,对象处于激活状态。为 FALSE 时,对象处于未激活状态
第四个参数:指向事件对象名的指针(只有时间对象在进程间共享时才有意义)
SetEvent( HANDLE hEvent) 函数:
设置事件对象为激活状态
ResetEvent( HANDLE hEvent) 函数:
设置事件对象为未激活状态
线程本地存储:
由于同一进程中的线程共享全局变量,而使用同一函数的线程共享函数内的静态局部变量(以上变量都为公用变量)。
函数内的局部变量在线程中被分配到堆栈,是线程的私有变量,但当线程退出该函数时,函数中的局部变量的数据将被清除(变量为线程私有变量,但函数退后后自动清除)
那么,怎样设置一种变量,让这种变量即使线程的私有变量,又可以在函数退出后被存储起来?那种变量就是线程本地存储(Thread-Local_Storage TLS)
在标准的 C 语言中并不支持这种数据,但在 WIndow 的 API 函数中,支持这种机制
1、定义一个含有所有数据的数据结构
typedef struct
{
int a;
........(其他数据)
}DATA, PDATA*;
2、在主线程中调用 API 函数中的 TlsAlloc 函数,返回一个指针,通过参数传递给线程
HANDLE Index;
Index = TlsAlloc();
3、分配全局变量,绑定该指针
TlsSetValue( Index, GlobalAlloc( GPTR, sizeof(DATA)));
4、在线程中使用该指针指向的内容
ThreadProc()
{
PDATA pdata;
pdata = (PDATA) TlsGetValue( Index);
}
typedef struct{ int a; int b; int c}DATA, PDATA *;void ThreadProc(LPVOID p){ PDATA pdata; pdata = (PDATA)TlsGetValue( (PDATA)p);}LRESULT CALLBACK WndProc(HWND hwnd, UINT message, LPARAM lParam, WPARAM wParam){ HADNLE Index; ........ case WM_CREATE: index = TlsAlloc();
TlsSetValue(index, GlobalAlloc(sizeof(DATA))); _beginthread(ThreadProc1, 0, (LPVOID)Index);
break;}
// 线程本地局部变量
_declspec (thread) int iGlobal = 1;
// 线程本地静态变量
_declspec (thread) static int iLocal = 2;
- 2、多线程及多线程间同步执行
- 多线程及同步
- Linux多线程及同步
- MFC 线程创建、多线程、及多线程间的同步问题
- Linux多线程及线程间同步
- MFC 多线程及线程同步
- MFC 多线程及线程同步
- MFC 多线程及线程同步
- Linux多线程通信及同步
- MFC多线程及线程同步
- MFC 多线程及线程同步
- MFC 多线程及线程同步
- MFC 多线程及线程同步
- MFC 多线程及线程同步
- MFC 多线程及线程同步
- MFC 多线程及线程同步
- MFC 多线程及线程同步
- Linux多线程通信及同步
- sunburnt 学习笔记(七)删除文档
- 关于Spring中AOP的几种拦截方式
- 生成XML——XmlSerializer
- cout与printf区别
- oc 浅 复 制 深 复 制 完全复制
- 2、多线程及多线程间同步执行
- onbeforedunload事件
- Java中如何一次跳出多层循环
- python离线安装setuptools
- 解析Xml——XmlPullParser
- KM算法
- SecureCRT 讲解以及安装
- 加载大图片到内存,进行缩放
- 从Excel导入数据到vi文件中遇到问题解决