线程同步之关键代码段
关键代码段:
1) 关键代码段(临界区)工作在用户方式下。
2) 关键代码段(临界区)是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。
用InitializeCriticalSection来初始化临界区,最后用DeleteCriticalSection来释放临界区资源。在线程中用EnterCriticalSection进入关键代码段,以获得指定的临界区对象的所有权,该函数等待指定的临界区对象的所有权,如果该所有权赋予了调用线程,则函数返回,否则该函数会一直等待,从而导致线程等待。LeaveCriticalSection释放指定临界区对象的所有权。
三种实现同步方法即使用互斥对象、事件对象与关键代码段的比较
1) 互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。
2) 关键代码段是工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。
利用关键代码段实现线程同步(以购买车票为例):
#include "windows.h"
#include <iostream>DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);int tickets = 100;
CRITICAL_SECTION g_cs;void main()
{HANDLE hTread1;HANDLE hTread2;hTread1= CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);hTread2= CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);CloseHandle(hTread1);CloseHandle(hTread1);InitializeCriticalSection(&g_cs);Sleep(4000);DeleteCriticalSection(&g_cs);
}DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{while (true){EnterCriticalSection(&g_cs);Sleep(1);if (tickets>0){Sleep(1);std::cout<<"thread1 sell ticket: "<<tickets--<<std::endl;LeaveCriticalSection(&g_cs);}else{LeaveCriticalSection(&g_cs);break;}}return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{while (true){EnterCriticalSection(&g_cs);Sleep(1);if (tickets>0){Sleep(1);std::cout<<"thread2 sell ticket: "<<tickets--<<std::endl;LeaveCriticalSection(&g_cs);}else{LeaveCriticalSection(&g_cs);break;}}return 0;
}
因为多个线程需要访问临界区对象,因此将其定义为全局变量,即上述代码定义的CRITICAL_SECTION 类型的对象:g_cs
首先在main中调用InitializeCriticalSection函数创建临界区对象,并在程序退出前,需要调用DeleteCriticalSection函数释放没有被任何线程使用的临界区对象那个的所有资源。
在主线程中创建的连个线程中,在进入关键代码段访问受保护的代码之前,需要调用EnterCriticalSection函数,以判断是否能得到指定的临界区对象的所有权,如果无法得到该多有权,那么EnterCriticalSection函数会一直等待,从而导致线程暂停运行;如果能够得到该所有权,那么该线程就进入到关键代码段,访问受保护的资源。
注意:在得到临界区对象的所有权之后,一定要释放该所有权。
但由于很容易因为粗心大意引起关键代码段造成死锁问题,因此使用时要特别小心。哲学家进餐问题是典型的线程死锁问题,其描述为:多位哲学家一起用餐,但每人只用一个筷子,当然一根筷子是无法吃到食物的,这是如果有一位哲学家愿意交出他自己的筷子,让其他人先吃,之后再将一双筷子交回来,这样所有的哲学家就都能吃到食物了。但是哲学家考虑的比较多,他们担心把筷子交给别人先吃,那么别人吃完食物之后不把筷子交回来的话,自己就吃不到食物了,所以他们都希望其他人先把筷子交出来,让自己先吃。然而由于每位哲学家都这样想,从而导致每位这削减都不肯交出筷子,于是所有的哲学家看着满桌子的美食,可就是吃不到,这就是一个线程死锁问题的实例。
对多线程来说,如果线程1拥有了临界区对象A,等待临界区对象B的拥有权;线程2拥有了临界区对象B,等待临界区对象A的拥有权,这就造成了死锁,下面通过程序演示死锁的发生:
#include "windows.h"
#include <iostream>DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);int tickets = 100;
CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;void main()
{HANDLE hTread1;HANDLE hTread2;hTread1= CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);hTread2= CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);CloseHandle(hTread1);CloseHandle(hTread1);InitializeCriticalSection(&g_csA);InitializeCriticalSection(&g_csB);Sleep(4000);DeleteCriticalSection(&g_csA);DeleteCriticalSection(&g_csB);
}DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{while (true){EnterCriticalSection(&g_csA);Sleep(1);EnterCriticalSection(&g_csB);if (tickets>0){Sleep(1);std::cout<<"thread1 sell ticket: "<<tickets--<<std::endl;LeaveCriticalSection(&g_csB);LeaveCriticalSection(&g_csA);}else{LeaveCriticalSection(&g_csB);LeaveCriticalSection(&g_csA);break;}}return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{while (true){EnterCriticalSection(&g_csB);Sleep(1);EnterCriticalSection(&g_csA);if (tickets>0){Sleep(1);std::cout<<"thread2 sell ticket: "<<tickets--<<std::endl;LeaveCriticalSection(&g_csA);LeaveCriticalSection(&g_csB);}else{LeaveCriticalSection(&g_csA);LeaveCriticalSection(&g_csB);break;}}return 0;
}
首先创建了两个临界区对象:g_csA, g_csB。在main()中,调用InitializeCriticalSection函数对两个对象进行了初始化,并在程序退出之前调用了DeleteCriticalSection释放临界区对象的所有资源;然后再线程1中调用EnterCriticalSection函数请求临界区对象:g_csA的所有权,当得到该所有权后,再去请求临界区对象g_csB的所有权,线程1访问完保护的资源后,调用LeaveCriticalSection函数释放两个临界区对象的所有权。 注意:因为调用LeaveCriticalSection时,该函数会立即返回,并不会导致线程等待,所以释放临界区对象所有权的顺序是无所谓的。
对线程2来说,它先请求临界区对象g_csB的所有权,然后再去等待临界区对象g_csA的所有权。在访问完保护资源后,释放所有临界区对象的所有权。
下面我们分析如何造成了死锁:
当线程1 得到临界区对象g_csA的所有权之后,调用Sleep()函数,该线程将休眠1毫秒,放弃执行机会。于是,操作系统会选择线程2来执行,该线程首先等待的是临界区对象g_csB的所有权,当它得到该所有权之后,调用Sleep()函数,让线程2也休眠1毫秒。于是轮到线程1执行,这是它需要等待临界区对象g_csB的所有权。然而这时临界区 对象g_csB已经被线程2所拥有,因此线程1就会等待,当线程1等待时,线程2就会执行,这时它需要等待临界区g_csA的所有权,然而临界区对象g_csA的所有权已经被线程1所拥有,因此线程2也进入等待状态。从而导致线程1和线程2都在等待对方交出临界区对象的所有权,于是就造成了死锁。
线程同步之关键代码段相关推荐
- 线程同步--关键代码段(三)
前面讲述了如何使用关键代码段. 但是关键代码段还有一些不为人知的秘密,也是多数程序员忽略的东西,特别是在多核cpu上面,我们必须知道的东西. 下面是<windows核心编程>这本书里面的一 ...
- Windows编程-- 用户方式中线程的同步---关键代码段(临界区)
可以从例子学习,更好的掌握 #include <windows.h> #include <iostream.h> //两个线程的声明 DWORD WINAPI Fun1Proc ...
- 线程同步--关键代码段(一)
线程同步有四种方式 但是在一个进程中,效率最高的,方式是 :关键代码段 #include <iostream> #include <windows.h> #include &l ...
- VC++中多线程学习(MFC多线程)三(线程同步包含:原子互锁、关键代码段、互斥器Mutex、Semaphores(信号量)、Event Objects(事件))
目录 线程同步的必要性: 2.解决同步问题的方法 2.1原子互锁家族函数 2.2Critical Sections(关键代码段.关键区域.临界区域) 2.3 互斥器Mutex ...
- 【Window】线程同步方式1——临界区(关键代码段)
第一节:[Window]创建线程的3种方式 第二节:[Window]线程同步概述 第三节:[Window]线程同步方式1--临界区(关键代码段) 第四节:[Window]线程同步方式2--互斥量 第五 ...
- 互斥对象与关键代码段的比较
9.6.2 互斥对象与关键代码段的比较 就等待线程的调度而言,互斥对象与关键代码段之间有着相同的特性.但是它们在其他属 性方面却各不相同.表 9 - 1 对它们进行了各方面的比较. 表 9-1 互斥对 ...
- 线程同步--关键代码段(二)
在我们接触到的多线程书籍里面,提到最多的就是线程同步问题了. 但是,我们看到最多的例子也是对一个临界资源的访问. 但是当我们自认为感觉靴子很好的时候,问题出现了,怎么才能够使我们让线程按照一定的顺序访 ...
- 秒杀多线程第九篇 经典线程同步总结 关键段 事件 互斥量 信号量
前面<秒杀多线程第四篇一个经典的多线程同步问题>提出了一个经典的多线程同步互斥问题,这个问题包括了主线程与子线程的同步,子线程间的互斥,是一道非常经典的多线程同步互斥问题范例,后面分别用了 ...
- 经典线程同步总结 关键段 事件 互斥量 信号量
本文参考了http://blog.csdn.net/morewindows/article/details/7538247 1.线程(进程)同步的主要任务 答:在引入多线程后,由于线程执行的异步性,会 ...
最新文章
- 关于Python3.9,看这张16岁高中生做的「新特性必知图」就够了
- java 线程组和线程_Java多线程 线程组原理及实例详解
- mysql数据库表空间最大值_mysql 数据库取最大值
- 52.Linux/Unix 系统编程手册(下) -- POSIX 消息队列
- UG软件_NX1926中文版网盘下载链接+安装教程
- 网络管理软件免费linux,SugarNMSTool免费版
- 物联网——无线通信技术
- BIG5, GB(GB2312, GBK, ...), Unicode编码, UTF8, WideChar, MultiByte, Char说明与区别
- 一篇教你随意下载网易云音乐歌曲的博客!
- 移植MotionDriver到RTT
- [转] 大学的终结—1950年代初期的“院系调整”
- 计算机导论——计算机软件03
- 老无所依nbsp;(聊后版)
- Linux dns劫持程序,linux的dns被劫持(解决方案)
- LSVGlobal Mapper应用----地形下载
- Whitelabel Error Page 的原因分析
- 电商运营基本常识你都知道哪些?
- 2021 年软件开发趋势大预测!
- 【汇正财经】什么是股票交割方式?股票交割方式有哪些?
- Python之禅——传说中的蛇宗总纲