多线程编程5种方法实现线程同步

摘要:
执行速度非常快。缺点是,如果要同步的部分需要很长时间才能执行,while循环将始终轮询操作,从而浪费CPU时间。在单核CPU系统中,while循环可能始终临时使用CPU,导致其他线程无法修改ISOK值,导致无法跳出while循环和无休止循环。此外,线程的优先级可能会导致问题。

1:用Interlocked系列函数实现线程同步;

2:用CRITICAL_SECTION及其系列函数实现线程同步;

3:用RTL_SRWLOCK及其系列函数实现线程同步;

4:用事件内核对象实现线程同步;

5:用信号量内核对象实现线程同步;

 

1:用Interlocked系列函数实现线程同步实例如下:

//旋转锁
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum];
volatile unsigned int ISOK=0;
unsigned int _stdcall Interlocked(PVOID threadId)
{ 
    while(InterlockedExchange(&ISOK,1)==1) ; 
    cout<<"线程:"<<*(int*)threadId<<"开始"<<endl; 
    Sleep(100);
    cout<<"线程:"<<*(int*)threadId<<"结束"<<endl; 
    InterlockedExchange(&ISOK,0);  
    return 0;
}
 
void InterlockedTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    cout<<"1:用Interlocked系列函数实现线程同步"<<endl;
    for(int i=0;i<10;i++){ 
        hThread[i]=(HANDLE)_beginthreadex(NULL, 0, Interlocked,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);   
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    } 
}

多线程编程5种方法实现线程同步第1张

InterlockedExchange确保以原子的方式操作数据。执行速度非常快,缺点是如果要同步的部分执行的时间比较长的话,while循环会一直轮询操作,浪费CPU的时间,在单核CPU的系统中,可能会出现while一直暂用CPU导致其他线程不能修改ISOK的值,导致不能跳出while循环,出现死循环。还有就是线程的优先级问题也能导致问题。

2:用CRITICAL_SECTION及其系列函数实现线程同步实例如下:

//关键段 
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum]; 
CRITICAL_SECTION g_cs;//构造一个CRITICAL_SECTION实例
unsigned int _stdcall  CriticalSection(PVOID threadId)
{ 
    EnterCriticalSection(&g_cs);//进入关键段
    cout<<"线程:"<<*(int*)threadId<<"开始"<<endl; 
    Sleep(100);
    cout<<"线程:"<<*(int*)threadId<<"结束"<<endl; 
    LeaveCriticalSection(&g_cs);//进入关键段 
    return 0;
}


void CriticalSectionTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    InitializeCriticalSection(&g_cs);//初始化g_cs的成员 
    cout<<"2:用CRITICAL_SECTION及其系列函数实现线程同步"<<endl;
    for(int i=0;i<10;i++){ 
        hThread[i]=(HANDLE)_beginthreadex(NULL, 0, CriticalSection,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);   
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    } 
    DeleteCriticalSection(&g_cs);//删除关键段
}

多线程编程5种方法实现线程同步第2张

CRITICAL_SECTION同样是以原子的方式操作数据,也只有以原子的方式操作数据才能实现线程的同步,所有实现线程同步的方法,最核心的部分就是以原子的方式操作数据,CRITICAL_SECTION执行的速度非常快,其内部有一个事件内核对象,当出现资源争夺的时候,才会出现初始化这个事件内核对象,由于CRITICAL_SECTION执行非常快可能不会出现资源争夺,也就没有必要创建这个事件内核对象,这个事件内核对象创建后,会将当前线程之外的线程挂起,并记录这些线程需要这个资源,其他线程就不会浪费CPU的时间,而这些被挂起的线程将由用户模式变成内核模式,当这些线程需要的资源可用时,系统会将其中一个线程唤醒。

还有一点值得注意:如果要同步的代码执行得很快,在出现争夺资源的时候,系统把其他线程挂起,而当前线程又马上执行完成了,系统又将挂起的线程唤醒,这个过程是非常浪费CPU的,也影响程序的性能,为了避免这种情况,可以结合旋转锁和CRITICAL_SECTION,先用旋转锁轮询一定次数,还不能获得资源,再将线程挂起,等待资源被释放,系统再将线程唤醒,实现这一功能的就是方法

InitializeCriticalSectionAndSpinCount(

   LPCRITICAL_SECTION lpCriticalSection,

   DWORD dwSpinCount//旋转锁轮询的次数

);

除了初始化CRITICAL_SECTION用的是方法InitializeCriticalSectionAndSpinCount,而不是方法InitializeCriticalSection,其他的都是一样的。

3:用RTL_SRWLOCK及其系列函数实现线程同步实例如下:

//读写锁 
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum]; 
RTL_SRWLOCK  lock;//构造一个CRITICAL_SECTION实例
unsigned int _stdcall  SrwLock(PVOID threadId)
{ 
    AcquireSRWLockExclusive(&lock);//进入读写锁
    cout<<"线程:"<<*(int*)threadId<<"开始"<<endl; 
    Sleep(100);
    cout<<"线程:"<<*(int*)threadId<<"结束"<<endl; 
    ReleaseSRWLockExclusive(&lock);//进入读写锁
    return 0;
}
 
void SrwLockTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    InitializeSRWLock(&lock);//初始化lock的成员 
    cout<<"3:用RTL_SRWLOCK及其系列函数实现线程同步"<<endl;
    for(int i=0;i<10;i++){ 
        hThread[i]=(HANDLE)_beginthreadex(NULL, 0, SrwLock,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);   
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    } 
     
}

多线程编程5种方法实现线程同步第3张

SRWLock的目的和关键段是一样的,就是对资源的保护,不让其他线程访问。不同的是,它区分线程是读线程还是写线程。我们都是知道,一个资源可以同时被多个线程同时读,就是不能同时读,或是读写。也是是说写必须是独占的方式,而读可以以共享的方式访问,如果以共享的方式访问肯定就比CRITICAL_SECTION性能好。

4:用事件内核对象实现线程同步实例如下:

//事件
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum];
HANDLE event1; 

unsigned int _stdcall  Event(PVOID threadId)
{
    WaitForSingleObject(event1,INFINITE);
    int* p=(int*)threadId;
    cout<<"线程:"<<*p<<"开始"<<endl; 
    Sleep(100);
    cout<<"线程:"<<*p<<"结束"<<endl;  
    SetEvent(event1);
    return 1;
}

void EventTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    event1=CreateEvent(NULL,false,true,NULL); 
    cout<<"4:用事件内核对象实现线程同步"<<endl;     
    for(int i=0;i<threadNum;i++)
    {
        hThread[i] =(HANDLE)_beginthreadex(NULL, 0, Event ,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);  
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    } 
    CloseHandle(event1);
}

多线程编程5种方法实现线程同步第4张

用内核对象实现线程同步,一个函数是必须知道的,它就是WaitForSingleObject。

DWORD WaitForSingleObject(

    HANDLE hHandle,//内核对象的句柄

    DWORD dwMilliseconds//等待时间

);

该函数会一直等待,直到被指定的内核对象被触发为止,或是等待的时间结束返回。

CreateEvent(

    LPSECURITY_ATTRIBUTES lpEventAttributes,//安全控制

    BOOL bManualReset,//true:手动重置事件,false:自动重置事件

    BOOL bInitialState,//true:有信号,false:无信号

    LPCWSTR lpName//事件名称

);

bManualReset为true表示事件触发了并一直处于触发状态,就像打开的门,打开之后就是一直开着,没有自动关上;false:一打开放一个进去进关了,需要用SetEvent再次触发事件。

5:用信号量内核对象实现线程同步实例如下:

//信号量
#include <iostream> 
using namespace std;  
#include <process.h>
#include <windows.h> 
const int threadNum=10;
HANDLE hThread[threadNum];
HANDLE semaphore; 
unsigned int _stdcall  Semaphore(PVOID threadId)
{
    WaitForSingleObject(semaphore, INFINITE);  
    cout<<"线程:"<<*(int*)threadId<<"开始"<<endl; 
    Sleep(100);
    cout<<"线程:"<<*(int*)threadId<<"结束"<<endl; 
    ReleaseSemaphore(semaphore,1,NULL); 
    return 0;
}
 
void SemaphoreTest()
{
    int threadId[threadNum];
    for(int i=0;i<threadNum;i++)
    {
        threadId[i]=i+1;
    }
    semaphore=CreateSemaphore(NULL,1,1,NULL);
    cout<<"5:用信号量内核对象实现线程同步"<<endl;
    for(int i=0;i<10;i++){ 
        hThread[i]=(HANDLE)_beginthreadex(NULL, 0, Semaphore,threadId+i, 0, NULL);  
    }
    WaitForMultipleObjects(threadNum, hThread, TRUE, INFINITE);   
    for(int i=0;i<threadNum;i++)
    {
        CloseHandle(hThread[i]);
    }
    CloseHandle(semaphore);
}

多线程编程5种方法实现线程同步第5张

信号量内核对象用来对资源进行计数。创建信号量内核对象的方法如下:

CreateSemaphore(

    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全控制

    LONG lInitialCount,//初始资源数量

    LONG lMaximumCount,//最大并发数量

    LPCWSTR lpName//号量的名称

);

lMaximumCount表示最大并发数量,可以用来设置系统的最大并发数量,如果我们把他的值设为1,lInitialCount也设为1,就是只有一个资源,且每次只能一个线程访问,这样就可以实现线程同步。

在实现线程同步时,建议用方法2和方法3,如不能解决你的需求,再用方法4,方法5,用内核对象实现的线程同步性能要差一些。

多线程编程5中实现线程同步的方法介绍差多就到此了,大家可能还有一些疑问,可以看看我之前关于多线程基础知识的一些介绍:

Windows线程基础

Windows内核对象简介

Windows几种线程同步方法介绍

免责声明:文章转载自《多线程编程5种方法实现线程同步》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇C#中的Sessionsqlsugar的sum的用法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

多线程和CPU的关系

什么是CPU (1)         Central  Progressing  Unit 中央处理器,是一块超大规模的集成电路,是一台计算机的运算核心和控制核心。 (2)         CPU包括 运算器,高速缓冲存储器,总线。 (3)         它的工作,主要是解释计算机中的指令,和处理计算机软件中的数据。它在计算机中起着最重要的作用,构成了系...

djangomysql的连接池

http://pypi.python.org/pypi/django-mysqlpool/0.1-7一个 SmartFile 的开源项目 介绍 这是一个简单的mysql连接池的数据库后端。这个后端实现源于Ed Menendez的一个博客文章:http://menendez.com/blog/mysql-connection-pooling-django-a...

面试官:Redis 单线程已经很快,为何 6.0要引入多线程?有啥优势?

作者:Java斗帝之路 链接:https://www.jianshu.com/p/ba2f082ff668 Redis作为一个基于内存的缓存系统,一直以高性能著称,因没有上下文切换以及无锁操作,即使在单线程处理情况下,读速度仍可达到11万次/s,写速度达到8.1万次/s。但是,单线程的设计也给Redis带来一些问题: 只能使用CPU一个核; 如果删除的键...

将dll文件注入到其他进程中的一种新方法

http://www.45it.com/windowszh/201212/33946.htm http://www.hx95.cn/Article/OS/201212/65095.html 我们知道将动态连接库注入到其他进程中有很多种方法。最常见的方法是使用钩子函数(Hook),但是这种方法主要有两个缺点:第一如果某个进程没有加载User32.dll,那么...

Jmeter(二)

1.简介 上一篇中宏哥已经教你把JMeter的测试环境搭建起来了,那么这一篇我们就将JMeter启动起来,一睹其芳容,首先宏哥给大家介绍一下如何来创建一个测试计划(Test Plan)。 2.创建一个测试计划(Test Plan) 测试计划(Test Plan)描述了一系列Jmeter运行时将要执行的一系列步骤。完整的测试计划包含一个或者多个线程组,逻辑控...

windows 最大支持线程数

WINDOWS操作系统中可以允许最大的线程数 默认情况下,一个线程的栈要预留1M的内存空间 而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程 但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。 你也可以通过连接时修改默认栈大小,将其改的比较小,这样就可以多开一些线程。 如将默认栈的大小改成512K,这样理论...