最近面试的许多公司都询问关于多线程的问题,但是问的深度一般不会很难,仅仅问相关的同步问题以及对应的API函数,下面是windows下几个常用的同步方法,对于应付帮助或者一般的开发都非常有用

目录
一 临界区
二 互斥体
三 事件
四 信号量
五  附录

一 临界区

临界区的使用在线程同步中应该算是比较简单,说它简单还是说它同后面讲到的其它方法相比更容易理解。举个简单的例子:比如说有一个全局变量(公共资源)两个线程都会对它进行写操作和读操作,如果我们在这里不加以控制,会产生意想不到的结果。假设线程A正在把全局变量加1然后打印在屏幕上,但是这时切换到线程B,线程B又把全局变量加1然后又切换到线程A,这时候线程A打印的结果就不是程序想要的结果,也就产生了错误。解决的办法就是设置一个区域,让线程A在操纵全局变量的时候进行加锁,线程B如果想操纵这个全局变量就要等待线程A释放这个锁,这个也就是临界区的概念。

使用方法:
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
...
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

/****************************************************************
*在使用临界区的时候要注意,每一个共享资源就有一个CRITICAL_SECTION
*如果要一次访问多个共享变量,各个线程要保证访问的顺序一致,如果不
*一致,很可能发生死锁。例如:
*   thread one:
*   EnterCriticalSection(&c1)
*   EnterCriticalSection(&c2)
*   ...
*   Leave...
*   Leave...
*
*   thread two:
*   EnterCriticalSection(&c2);
*   EnterCriticalSection(&c1);
*   ...
*   Leave...
*   Leave...
*这样的情况就会发生死锁,应该让线程2进入临界区的顺序同线程1相同
****************************************************************/

const int MAX_THREADNUMS = 4;            //产生线程数目
CRITICAL_SECTION cs;                             //临界区
HANDLE event[MAX_THREADNUMS];       //保存createevent的返回handle
int critical_value = 0;                                   //共享资源

UINT WINAPI ThreadFunc(void* arg)
{
    int thread = (int)arg;
    for (int i = 0; i < 5; i++)
    {    
        EnterCriticalSection(&cs);
        cout << "thread " << thread << " ";

critical_value++;
        cout << "critical_value = " << critical_value << endl;     
        LeaveCriticalSection(&cs);
    }
    SetEvent(event[thread]);
    return 1;
}

int main(int argc, char* argv[])
{
    cout << "this is a critical_section test program" << endl;
    HANDLE hThread;
    UINT uThreadID;
    DWORD dwWaitRet = 0;
  
    InitializeCriticalSection(&cs);

for (int i = 0; i < MAX_THREADNUMS; i++)
    {
        event[i] = CreateEvent(NULL, TRUE, FALSE, "");
        if (event[i] == NULL)
        {
            cout << "create event " << i << " failed with code: " 
                << GetLastError() << endl;
            continue;
        }
        hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, 
            (void*)i, 0, &uThreadID);
        if (hThread == 0)
        {
            cout << "begin thread " << i << " failed with code: " 
                << GetLastError() << endl;
            continue;
        }
        CloseHandle(hThread);
    }
    //等待所有线程完成
    dwWaitRet = WaitForMultipleObjects(MAX_THREADNUMS, event, TRUE, INFINITE);
    switch(dwWaitRet)
    {
    case WAIT_OBJECT_0:

cout << "all the sub thread has exit!" << endl;
        break;
    default:
        cout << "wait for all the thread failed with code:" << GetLastError() << endl;
        break;
    }
    
    DeleteCriticalSection(&cs);
    for (int k = 0; k < MAX_THREADNUMS; k++)
    {
        CloseHandle(event[k]);
    }
 return 0;
}

二 互斥体
windows api中提供了一个互斥体,功能上要比临界区强大。也许你要问,这个东东和临界区有什么区别,为什么强大?它们有以下几点不一致:
1.critical section是局部对象,而mutex是核心对象。因此像waitforsingleobject是不可以等待临界区的。
2.critical section是快速高效的,而mutex同其相比要慢很多
3.critical section使用范围是单一进程中的各个线程,而mutex由于可以有一个名字,因此它是可以应用于不同的进程,当然也可以应用于同一个进程中的不同线程。
4.critical section 无法检测到是否被某一个线程释放,而mutex在某一个线程结束之后会产生一个abandoned的信息。同时mutex只能被拥有它的线程释放。下面举两个应用mutex的例子,一个是程序只能运行一个实例,也就是说同一个程序如果已经运行了,就不能再运行了;另一个是关于非常经典的哲学家吃饭问题的例子。

程序运行单个实例:
#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

//当输入s或者c时候结束程序
void PrintInfo(HANDLE& h, char t)
{
    char c;
    while (1)
    {
        cin >> c;
        if (c == t)
        {
            ReleaseMutex(h);
            CloseHandle(h);
            break;
        }
        Sleep(100);
    }
}
int main(int argc, char* argv[])
{
    //创建mutex,当已经程序发现已经有这个mutex时候,就相当于openmutex
    HANDLE hHandle = CreateMutex(NULL, FALSE, "mutex_test");
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        cout << "you had run this program!" << endl;
        cout << "input c to close this window" << endl;
        PrintInfo(hHandle, 'c');
        return 1;
    }
    cout << "program run!" << endl;
    cout << "input s to exit program" <<endl;
    
    PrintInfo(hHandle, 's');
    return 1;
}

哲学家吃饭问题:

const int PHILOSOPHERS = 5;          //哲学家人数
const int TIME_EATING = 50;         //吃饭需要的时间 毫秒
HANDLE event[PHILOSOPHERS];    //主线程同工作线程保持同步的句柄数组
HANDLE mutex[PHILOSOPHERS];   //mutex数组,这里相当于公共资源筷子
CRITICAL_SECTION cs;                //控制打印的临界区变量

UINT WINAPI ThreadFunc(void* arg)
{
    int num = (int)arg;
    
    DWORD ret = 0;
    while (1)
    {
        ret = WaitForMultipleObjects(2, mutex, TRUE, 1000);
        if (ret == WAIT_TIMEOUT)
        {
            Sleep(100);
            continue;
        }
        EnterCriticalSection(&cs);
            cout << "philosopher " << num << " eatting" << endl;
        LeaveCriticalSection(&cs);
        Sleep(TIME_EATING);
        break;
    }
    //设置时间为有信号
    SetEvent(event[num]);
    return 1;
}
int main(int argc, char* argv[])
{
    HANDLE hThread;
    InitializeCriticalSection(&cs);
    //循环建立线程
    for (int i = 0; i < PHILOSOPHERS; i++)
    {
        mutex[i] = CreateMutex(NULL, FALSE, "");
        event[i] = CreateEvent(NULL, TRUE, FALSE, "");
        hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)i, 0, NULL);
        if (hThread == 0)
        {
            cout << "create thread " << i << "failed with code: " 
                << GetLastError() << endl;
            DeleteCriticalSection(&cs);
            return -1;
        }
        CloseHandle(hThread);
    }
    
    //等待所有的哲学家吃饭结束
    DWORD ret = WaitForMultipleObjects(PHILOSOPHERS, event, TRUE, INFINITE);
    if (ret == WAIT_OBJECT_0)
    {
        cout << "all the philosophers had a dinner!" << endl;
    }
    else
    {
        cout << "WaitForMultipleObjects failed with code: " << GetLastError() << endl;
    }
    DeleteCriticalSection(&cs);
    for (int j = 0; j < PHILOSOPHERS; j++)
    {
        CloseHandle(mutex[j]);
    }
    return 1;
}

三 事件
事件对象的特点是它可以应用在重叠I/O(overlapped I/0)上,比如说socket编程中有两种模型,一种是重叠I/0,一种是完成端口都是可以使用事件同步。它也是核心对象,因此可以被waitforsingleobje这些函数等待;事件可以有名字,因此可以被其他进程开启。我在前几个例子当中其实已经使用到event了,在这里就不多说了,可以参考前一个例子。

四 信号量
semaphore的概念理解起来可能要比mutex还难, 我先简单说一下创建信号量的函数,因为我在开始使用的时候没有很快弄清楚,可能现在还有理解不对的地方,如果有错误还是请大侠多多指教。
CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,  // SD
  LONG lInitialCount,                                               // initial count
  LONG lMaximumCount,                                            // maximum count
  LPCTSTR lpName                                                   // object name
)
第一个参数是安全性,可以使用默认的安全性选项NULL;第二个和第三个参数是两个long型的数值,它们表示什么含义呢?lMaxinumCount表示信号量的最大值,必须要大于零。比如是5就表示可以有5个进程或者线程使用,如果第六个进程或者线程想使用的话就必须进入等待队列等待有进程或者线程释放资源。lInitalCount表示信号量的初始值,应该大于或者等于零小于等于lMaximumCount。如果lInitialCount = 0 && lMaximumCount == 5,那么就表示当前资源已经全部被使用,如果再有进程或者线程想使用的话,信号量就会变成-1,该进程或者线程进入等待队列,直到有进程或者线程执行ReleaseMutex;如果lInitialCount = 5 && lMaximumCount == 5,那么就表示现在信号量可以被进程或者线程使用5次,再之后就要进行等待;如果InitialCount = 2 && MaximumCount == 5这样的用法不太常见,表示还可以调用两次CreateSemaphore或者OpenSemaphore,再调用的话就要进入等待状态。最后一个参数表示这个信号量的名字,这样就可以跨进程的时候通过这个名字OpenSemaphore。说了这么多了,不知道说明白没有 

看个例子,popo现在好像在本机只能运行三个实例,我们在前面说的mutex可以让程序只是运行一个实例,下面我通过信号量机制让程序像popo一样运行三个实例。

#include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;

const int MAX_RUNNUM = 3;  //最多运行实例个数
void PrintInfo()
{
    char c;
    cout << "run program" << endl;
    cout << "input s to exit program!" << endl;
    while (1)
    {
        cin >> c;
        if (c == 's')
        {
            break;
        }
        Sleep(10);
    }
}
int main(int argc, char* argv[])
{
    
    HANDLE hSe = CreateSemaphore(NULL, MAX_RUNNUM, MAX_RUNNUM, "semaphore_test");
    DWORD ret = 0;
    
    if (hSe == NULL)
    {
        cout << "createsemaphore failed with code: " << GetLastError() << endl;
        return -1;
    }
   
    
    ret = WaitForSingleObject(hSe, 1000);
    if (ret == WAIT_TIMEOUT)
    {
        cout << "you have runned " << MAX_RUNNUM << " program!" << endl;
        ret = WaitForSingleObject(hSe, INFINITE);  
    }
    
    PrintInfo(); 
    ReleaseSemaphore(hSe, 1, NULL);
    CloseHandle(hSe);
 return 0;
}

附录:
核心对象
Change notification 
Console input 
Event 
Job 
Mutex 
Process 
Semaphore 
Thread 
Waitable timer

同步机制及windows同步函数的使用的更多相关文章

  1. IT兄弟连 JavaWeb教程 使用Java同步机制对多线程同步

    对于前面AdderServlet案例,它的sum实例变量用来累计客户端请求进行加法运算的和.sum变量的初始为100,如果第一个客户请求加上100,那么sum变量变为200,接着第二个客户请求加上20 ...

  2. Linux内核同步机制

    http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环 ...

  3. 分析.Net里线程同步机制

    我 们知道并行编程模型两种:一种是基于消息式的,第二种是基于共享内存式的. 前段时间项目中遇到了第二种 使用多线程开发并行程序共享资源的问题 ,今天以实际案例出发对.net里的共享内存式的线程同步机制 ...

  4. Linux内核的同步机制

    本文详细的介绍了Linux内核中的同步机制:原子操作.信号量.读写信号量和自旋锁的API,使用要求以及一些典型示例 一.引言 在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程 ...

  5. C++ folly库解读(三)Synchronized —— 比std::lock_guard/std::unique_lock更易用、功能更强大的同步机制

    目录 传统同步方案的缺点 folly/Synchronized.h 简单使用 Synchronized的模板参数 withLock()/withRLock()/withWLock() -- 更易用的加 ...

  6. java多线程同步机制

    一.关键字: thread(线程).thread-safe(线程安全).intercurrent(并发的) synchronized(同步的).asynchronized(异步的). volatile ...

  7. Java多线程 | 02 | 线程同步机制

    同步机制简介 ​ 线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.Java平台提供的线程同步机制包括: 锁,volatile关键字,final关键字,static关键字,以 ...

  8. windows核心编程 - 线程同步机制

    线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...

  9. 【av68676164(p31-p32)】Windows和Linux同步机制

    4.6.1 Windows同步机制 临界区(CRITICAL_SECTION) 在进程内使用,保证仅一个线程可以申请到该对象 临界区内是临界资源的访问 相关的API函数 初始化临界区 WINBASEA ...

随机推荐

  1. 基于HTML5 Canvas可撕裂布料效果

    分享一款布料效果的 HTML5 Canvas 应用演示,效果逼真.你会看到,借助 Canvas 的强大绘图和动画功能,只需很少的代码就能实现让您屏息凝神的效果. 在线预览   源码下载 实现的代码. ...

  2. 在linux跑xenomai vkworks skin的测试

    1 代码 ############################################################## /* * Copyright (C) 2001,2002,200 ...

  3. Linux快速定位并且杀掉占用端口的进程

    1.定位 lsof -i:8811(端口号) 2.杀掉进程 kill -9 63924

  4. jvm 调整tomcat的堆内存和常驻内存catalina.sh

    4.2 性能优化 tomcat性能取决于你的内存大小 上策:优化代码 中策:jvm优化机制  垃圾回收机制 把不需要的内存回收   优化jvm--优化垃圾回收策略 优化catalina.sh配置文件. ...

  5. kettle两表内链接的查询结果与sql语句的查询结果不符合?

    1.教师表输入 2.学生表 查 3.学生表中查出的教师id进行排序 5.教师表中查出的同样也对教师的id进行排序 6.进行左连接 总结: 进行连接的时候的关键是同样对教师的id进行先排序

  6. js实现EasyUI-datagrid前台分页

    //实现假分页 function myLoader(param, success, error) { var that = $(this); var opts = that.datagrid(&quo ...

  7. 【转】【Linux】sed命令详解

    sed命令详解 sed是stream editor的简称,也就是流编辑器.它一次处理一行内容,处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令 ...

  8. 第二百七十七节,MySQL数据库-数据表、以及列的增删改查

    MySQL数据库-数据表.以及列的增删改查 1.创建一个表 CREATE(创建) TABLE(表) ENGINE(引擎) ENGINE=INNODB(引擎)还有很多类引擎,这里只是简单的提一下INNO ...

  9. css 五角星 (转)

    1.前言 之前做的好几个项目中,都会遇到打分,评分,点赞这样的需求,写了很多次,每次需要再写的时候,就会翻出之前写过的代码,然后copy过来.总觉得这样的话没有进步,没有把知识放进脑袋里,所以,自己花 ...

  10. Struts2_day01--导入源文件_Struts2的执行过程_查看源代码

    导入源文件 选中按ctrl + shift + t进入 Struts2执行过程 画图分析过程 过滤器在服务器启动时创建,servlet在第一次访问时创建 查看源代码 public class Stru ...