go 的并发调度(一) GMP 模型

go 的并发调度(一) GMP 模型,第1张

原文出自: Golang调度器GPM原理与调度全分析

M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的。

老调度器有几个缺点:

面对之前调度器的问题,Go设计了新的调度器。

新的调度器引入了P的概念:Processor,它包含了运行goroutine的资源,如果线程想运行goroutine,必须先获取P,P中还包含了可运行的G队列。

这意味着在程序执行的任意时刻都只有$GOMAXPROCS个goroutine在同时运行。

M与P的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来。

从上图我们可以分析出几个结论:

1、我们通过 go func()来创建一个goroutine;

2、有两个存储G的队列,一个是局部调度器P的本地队列、一个是全局G队列。新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列中;

3、G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系。M会从P的本地队列d出一个可执行状态的G来执行,如果P的本地队列为空,就会想其他的MP组合偷取一个可执行的G来执行;

4、一个M调度G执行的过程是一个循环机制;

5、当M执行某一个G时候如果发生了syscall或则其余阻塞 *** 作,M会阻塞,如果当前有一些G在执行,runtime会把这个线程M从P中摘除(detach),然后再创建一个新的 *** 作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P;

6、当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中。

优化建议:

1 在你的代码里大点评测下 每个 *** 作花的时间 譬如 dom4j解析花了多久, 存储到数据库花了多久等等

2 评测哪些地方可以并行 *** 作以提高CPU利用率;

3 数据库 *** 作部分也可以做适当优化, 譬如批量提交可以显著提高插入速度, 譬如去除索引/主键后插入等;

4 不同机器的IO速度是不同的, 因此应该能提供运行时的任务调度参数化, 譬如多少个dom4j解析线程, 入库的批量数量等;

答:数据库的完整性是指数据的正确性和相容性。 问题:数据库的完整性概念与数据库的安全性概念有什么区别和联系?答: 数据库的完整性是指数据的正确性和相容性、数据库的安全性是指保护数据库.以防止不合法的使用造成的数据泄密、更改或破坏。其相同点是两者都是对数据库中的数据进行控制.各自所实现的功能目标不同。问题:什么是数据库的完整性约束条件?可分为哪几类?答: 数据完整性约束是为了保证进入数据库中的数据的有效性而定义的数据规则、它可以分为以下两类. ①针对不同的对象可以分为表级约束、元塑级约束和属性级约束(也称列约束);表级约束是若干元组间、关系中及关系之间的约束:元组级约束则是元组中的字段组和字段间联系的约束;属性级约束主要是针对列的类型、取值范围、精度、排序等而制定的约束条件。②针对数据对象的状态可以分为静态约束和动态约束:静态约束是指数据库每一确定状态时的数据对象所应满足的约束条件.它是反映数据库状态稳定时的约束.动态约束是指数据库从一种状态转变为另一种状态时.新、旧值之间所应满足的约束条件.它是反映数据库状态变迁的约束。 问题: DBMS的完整性控制应具有哪些功能? 答;①定义和存储完整性功能.②检查完整性功能;③控制完整性功能。 问题:RDBMS在实现参照完整性时需要考虑哪些方面?答: ①外码能够接受空值的问题. ②在被参照关系中删除元组时.采用级联删除、受限删除或置生值删除的方法处理参照关系; ③在参照关系中插入元组时.可以使用受限插入、递归插入两种方法处理参照关系. ④修改关系的主码时 可以采用不允许修改主码、或允许修改关系主码.但必须保证主码的惟一性和非空性方法处理参照关系; ⑤修改被参照关系时,可以采用级联修改、拒绝修改和置空值修改方法处理参照关系。问题:假设有下面两个关系模式: 职工(职工号,姓名,年龄,职务,工资,部门号),其中职工号为主码; 部门(部门号,名称,经理名,电话),其中部门号为主码.用SQL语言定义这两个关系模式.要求在模式中完成以下完整性约束条件的定义: 1)定义每个模式的主码。 2)定义参照完整性。3)定义职工年龄不得超过60岁。 答: CREATE TABLE职工(职工号 CHAR(5)PRIMARY KEY, 姓名CHAR(8)NOT NULL, 年龄SMALLINT. 职务CHAR(10), 工资DECIMAL(7,2), 部门号CHAR(5)。 CONSTRAINT CI CHECK(年龄 <60). CONSTRAIN C2 FOREIGN KEY(部门号) REFEENCES部门(部门号)); CREAT TABLE部门(部门号CHAR(5)PRIMARY KEY. 名称CHAR(l). 经理名 CHAR(8). 电话 CHAR(8). CONSTRAINT C3 FOREIGN KEY(经理名) REFERECES职工(姓名));问题:在数据库中为什么要并发控制?答; 数据库的井发控制就是为了控制数据库,防止多用户并发使用数据库时造成数据错误和程序运行错误,保证数据的完整性。问题:并发 *** 作可能会产生哪几类数据不一致?用什么方法能避免这些不一致的情况?答. 井发 *** 作可能会产生丢失修改、不可重复读和读“脏”数据的数据不一致问题。用封锁的方法能避免这些不一致的情况。问题:什么是封锁?答. 封锁是使事务对它要 *** 作的数据有一定的控制能力。封锁具有三个环节.第一个环节是申请加锁.第二个环节是获得锁;第三个环节是释放锁。问题:基本的封锁类型有几种?试述它们的含义。 答. 基本的封锁类型有两种:排它锁(简称X锁)和共享锁(简称S锁)。 排它锁也称为独占或写锁、一旦事务T对数据对象A加上排它锁.则只允许T读取和修改A.其他任何事务既不能读取和修改A,也不能再对A加任何类型的锁 直到T释放A上的锁为止。 共享锁又称读锁、如果事务T对数据对象A加上共享锁,其他事务只能再对A加S锁,不能加X锁,知道事务T释放A上的S锁为止。问题:如何用封锁机制保证数据的一致性?答: 封锁机制作为井发控制的重要手段.利用封锁的特性和封锁协议,它在井发 *** 作保证事务的隔离性.用正确的方式调度并发 *** 作.是一个用户事务的执行不受其他事务的干扰.从而避免造成数据的不一致性。问题:什么是封锁协议?不同级别的封锁协议的主要区别是什么? 答. 在对数据对象加锁时,还需要约定一些规则 这些规则称为封锁协议。 一级封锁协议:是事务T在修改数据之前必须先对其加X锁.直到事务结束才释放。一级封锁协议可有效地防止丢失修改并能够保证事务T的可恢复性、一级封锁由于没有对数据进行加锁,所以不能保证可重复读和不读“赃”数据。 二级封锁协议;是事务T对要修改的数据必须先加X锁.直到事务结束才释放X锁;要读取的数据必须先加S锁.读完后即可释放S锁。M级封锁协议不但能够防止丢失修改,还可进一步防止读“脏”数据。 三级封锁协议:是事务T在读取数据之前必须先对其加S锁.在要修改数据之前必须先对其加X锁.直到事务结束后才释放所有锁、由于三级封锁协议强调即使事务读完数据A之后也不释放S锁 从而使得别的事务无法更改数据A、三级封锁协议不但防止了丢失修改和不读“脏”数据,而且防止了不可重复的队问题:不同封锁协议与系统一致性级别的关系是什么?答: 一级封锁协议可有效地防止丢失修改,并能够保证事务T的可恢复性。一级封锁由于没有对数据进行加锁,所以不能保证可重复读和不读“脏’数据。 二级封锁协议不但能够防止丢失修改.还可进一步防u读“脏”数据。 由于三级封锁协议强调即使事务读完数据A之后也不释放S锁,从而使别的事务无法更改数据A。三级封锁协议不但防止了丢失修改和不读“胜数据.而且防u了不可重复读。问题:什么是活锁?什么是死锁?答; 在多个事务请求对同一数据封锁时,总是使某一用户等待的情况称为活锁;多事务交错等待的僵持局面称为死锁。问题:试述活锁的产生原因和解决方法。答; 活锁是封锁的无序造成的、解决方法是采用先来先服务的方法,即对要求封锁数据的事务排队.使前面的事务先获得数据的封锁权。问题:请给出预防死锁的若干方法。 答: 预防死锁通常有以下两种方法; ①一次封锁法.就是要求每个事务必须一次将所有要使用的数据全部加锁.否则该事务不能继续执行. ②顺序封锁法.是预先对数据对象规定一个封锁顺序.所有事务都按这个顺序实行封锁。问题:请给出检测死锁发生的一种方法,当发生死锁后如何解除死锁?答: 检测死锁发生的一种方法是选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有锁.使其他事务得以继续运行下去。 解除死锁问题有两类方法:一类方法是采用一定措施来预防死锁的发生.另一类方法是允许发生死锁.然后采用一定手段定期诊断系统中有无死锁.若有则解除之。问题:什么样的并发调度是正确的调度?答. 如果一个事务运行过程中没有其他事务同时运行,即没有受到其他事务的干扰,那么就可以认为该事务的运行结果是正常的,可串行性是井发事务正确性的准则 为了保证并发 *** 作的正确性.DBMS的并发控制机制必须提供一定的手段来保证调度是可串行化的。问题:试述两段锁协议的概念。 答: 所谓两段锁协议是指所有事务必须分两个阶段对数据项进行加锁和解锁. ①在对任何数据进行读、写 *** 作之前.首先要申请并获得对该数据的封锁. ②在释放一个封锁之后,事务不再申请并获得对该数据的封锁。 即每个事务分成两个阶段,第一阶段是申请和获得封锁,也称为扩展阶段。在这阶段.事务可以申请获得任何数据项上的任何类型的锁,但是不能释放任何锁。第二阶段是释放到锁.也称为收缩阶段。在这阶段,事务可以释放任何数据项上的任何类型的锁。但是不能再申请任何锁。问题:为什么要引进意向锁?意向锁的含义是什么?答: 事务 T要对关系 RI加 X锁时,系统只需检查根结点数据库和关系 RI是否已加了不相容的锁.而不再需要搜索和检查RI中的每一个元组是否加了X锁.对任一元组加锁.必须先对它所在的关系加意向锁。 意向锁的含义是.如果对一个结点加意向锁。则说明该给点的下层结点正在被加销:对任何一结加锁时.必须先对它的上层结点加意向锁。问题:理解并解释下列术语的含义:封锁、活锁、死锁、排它锁、共享锁、并发事务的调度、可串行化的调度、两段锁协议。答: ①封锁.封锁是使事务对它要 *** 作的数据有一定的控制能力。 ③活锁:这种在多个事务请求对同一数据封锁时.总是使某一用户等待的情况称为活锁。 ③死锁.这种多事务交错等待的僵持局面称为死锁。 ④排它锁.排名锁也称为独占或写锁、一旦事务T对数据对象A加上排它锁,则只允许T读取和修改A.其他任何事务既不能读取和修改A.也不能再对A加任何类型的锁.直到T释放A上的锁为止。 ⑤共享锁:共享锁又称读锁、如果事务T对数据对象A加上共享锁.其他事务只能再对A加S锁.不能加X锁.知道事务T释放A上的S锁为上。 ③井发事务的调度.多个事务并发执行调度策略称为并发事务的调度。 ①可串行化的调度:如果多个事务并发执行的结果与按串行执行的结果相同 这种调度策略称为可串行化的调度。③两段锁协议.所谓两段锁协议是指所有事务必须分两个阶段对数据项进行加锁和解锁。 问题:什么是数据库的安全性?答.数据库的安全性是指保护数据库.以防止不合法的使用数据泄密、更改或破坏。 问题:数据库安全性和计算机系统的安全性有什么关系? 答: 数据库安全性是计算机系统的安全性的一个部分.数据库系统不仅要利用计算机系统的安全性保证自己系统的安全性.同时还会提供专门的手段和方法,使安全性能更好。例如在用户要求进入计算机系统时.系统首先根据用户输入的用户标识进行身份鉴定,只有合法的用户才准许进入计算机系统:对已进入的用户 ***S还要进行存取控制,只允许用户执行合法 *** 作: *** 作系统也会提供相应的保护措施;数据最后还可以以密码形式存储到数据库中。 问题:试述实现数据库安全性控制的常用方法和技术。答. ①用户标识与鉴别:②存取控制:③自主存取控制方法.④强制存取控制方法:⑤视图机制;③审计.o数据加密。 问题:SQL 语言中提供了哪些数据控制(自主存取控制)的语句?请试举几例说明它们的使用方法。答. ①GRANT(授权)语句 例:GRANT SELECTINSRRT ON学生 TO张勇 WITH GRANT OPTION; ②REVOKE(收回)语句 例:REVOKE INSERT ON学生 FROM张勇; 问题:今有两个关系模式: 职工(职工号,姓名,年龄,职务,工资,部门号); 部门(部门号,名称,经理名,地址,电话)。 请田SQL 的GRANT和REVOKE语句(加上视图机制),完成以下授权定义或存取控制功能。 1)用户王明对两个表有SELECT权力。 2)用户李勇对两个表有INSERT和DELETE权力。 3)用户刘星对职工表有SELECT权利,对工资字段具有更新权力。 4)用户张新具有修改这两个表的结构的权力。 5)用户周平具有对两个表的所有权力(读、插、改、删数据),并具有给其他用户授权的权利。

1有组织,可共享

2实体完整性,参照完整性

3网状模型关系模型

4一致性,原子性

5数据不一致数据丢失

6外模式,内模式

7对内储存,对外储存

8读锁(s)写锁(x)

9E-R图

10消除了非主属性对码的传递函数依赖

1物理的逻辑独立性就是数据的存储和数据库系统相互独立,数据库里的数据单独存在于数据库 *** 作系统之外

2网状模型的优点:分层明确,且每个结点的数据都相互独立缺点:关系表现形式太复杂,不容易明显看清楚

3由于需要对数据进行增删改查,所以需要对数据修改做保护,避免出现增删改查的异常

4内模式是指数据库的存储模式,单纯的数据库数据存储

1∏sno,sname,sage,sdept(student)σ

2∏sdept(σsno,sname,sage,sdept(student))

3∏ name,sdept(σcno='1'∧(studentcoursec)

1select sno,sname,sage,sdept

from student

2select sname,sage

from student

where sdept='IS' and sdept='MS' and sdept='CS'

3select sno,sname,sage,sdept,cno,grade

from student,sc

where student sno=sc sno

五事物T1 事物T2

读A

读B

B=A+1

写回B

A=B+1

写回A

六不明白此题我的题不这样的问

七同上

八仓库 (库号,面积,电话号码)仓库号是主码

零件 (零件号,名称,规格,单价,描述)零件号是主码

供应商(供应商号,姓名,地址,电话号码,账号)供应商是主码

项目(项目号,预算,开工日期)项目号是主码

职工(职工号,姓名,年龄,职称)职工号是主码

满足两段锁协议的调度一定是正确的。两段锁协议:一个事务中一旦开始释放锁,就不能再申请新锁了。事务的加锁和解锁严格分为两个阶段,第一阶段加锁,第二阶段解锁。两段锁协议的目的是保证并发调度的正确性。如果所有 *** 作数据库的事务都满足两段锁协议,那么这些事务的任何并发调度策略是可串行性的。

现在流行的进程线程同步互斥的控制机制,其实是由最原始最基本的4种方法实现的。由这4种方法组合优化就有了Net和Java下灵活多变的,编程简便的线程进程控制手段。

这4种方法具体定义如下 在《 *** 作系统教程》ISBN 7-5053-6193-7 一书中可以找到更加详细的解释

1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。

2、互斥量:为协调共同对一个共享资源的单独访问而设计的。

3、信号量:为控制一个具有有限数量用户资源而设计。

4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

临界区(Critical Section)

保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式 *** 作共享资源的目的。

临界区包含两个 *** 作原语:

EnterCriticalSection() 进入临界区

 LeaveCriticalSection() 离开临界区

EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

MFC提供了很多功能完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。

//CriticalSection

CCriticalSection global_CriticalSection;

// 共享资源

char global_Array[256];

//初始化共享资源

void InitializeArray()

{

for(int i = 0;i<256;i++)

{

global_Array[i]=I;

}

}

//写线程

UINT Global_ThreadWrite(LPVOID pParam)

{

CEdit ptr=(CEdit )pParam;

ptr->SetWindowText("");

//进入临界区

global_CriticalSectionLock();

for(int i = 0;i<256;i++)

{

global_Array[i]=W;

ptr->SetWindowText(global_Array);

Sleep(10);

}

//离开临界区

global_CriticalSectionUnlock();

return 0;

}

//删除线程

UINT Global_ThreadDelete(LPVOID pParam)

{

CEdit ptr=(CEdit )pParam;

ptr->SetWindowText("");

//进入临界区

global_CriticalSectionLock();

for(int i = 0;i<256;i++)

{

global_Array[i]=D;

ptr->SetWindowText(global_Array);

Sleep(10);

}

//离开临界区

global_CriticalSectionUnlock();

return 0;

}

//创建线程并启动线程

void CCriticalSectionsDlg::OnBnClickedButtonLock()

{

//Start the first Thread

CWinThread ptrWrite = AfxBeginThread(Global_ThreadWrite,

&m_Write,

THREAD_PRIORITY_NORMAL,

0,

CREATE_SUSPENDED);

ptrWrite->ResumeThread();

//Start the second Thread

CWinThread ptrDelete = AfxBeginThread(Global_ThreadDelete,

&m_Delete,

THREAD_PRIORITY_NORMAL,

0,

CREATE_SUSPENDED);

ptrDelete->ResumeThread();

}

在测试程序中,Lock UnLock两个按钮分别实现,在有临界区保护共享资源的执行状态,和没有临界区保护共享资源的执行状态。

程序运行结果

互斥量(Mutex)

互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。

互斥量包含的几个 *** 作原语:

CreateMutex() 创建一个互斥量

OpenMutex() 打开一个互斥量

ReleaseMutex() 释放互斥量

WaitForMultipleObjects() 等待互斥量对象

同样MFC为互斥量提供有一个CMutex类。使用CMutex类实现互斥量 *** 作非常简单,但是要特别注意对CMutex的构造函数的调用

 CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)

 不用的参数不能乱填,乱填会出现一些意想不到的运行结果。

//创建互斥量

CMutex global_Mutex(0,0,0);

// 共享资源

char global_Array[256];

void InitializeArray()

{

for(int i = 0;i<256;i++)

{

global_Array[i]=I;

}

}

UINT Global_ThreadWrite(LPVOID pParam)

{

CEdit ptr=(CEdit )pParam;

ptr->SetWindowText("");

global_MutexLock();

for(int i = 0;i<256;i++)

{

global_Array[i]=W;

ptr->SetWindowText(global_Array);

Sleep(10);

}

global_MutexUnlock();

return 0;

}

UINT Global_ThreadDelete(LPVOID pParam)

{

CEdit ptr=(CEdit )pParam;

ptr->SetWindowText("");

global_MutexLock();

for(int i = 0;i<256;i++)

{

global_Array[i]=D;

ptr->SetWindowText(global_Array);

Sleep(10);

}

global_MutexUnlock();

return 0;

}

同样在测试程序中,Lock UnLock两个按钮分别实现,在有互斥量保护共享资源的执行状态,和没有互斥量保护共享资源的执行状态。

程序运行结果

信号量(Semaphores)

信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与 *** 作系统中的PV *** 作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。

PV *** 作及信号量的概念都是由荷兰科学家EWDijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。

P *** 作 申请资源:

 (1)S减1;

 (2)若S减1后仍大于等于零,则进程继续执行;

 (3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。

V *** 作 释放资源:

 (1)S加1;

 (2)若相加结果大于零,则进程继续执行;

 (3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。

信号量包含的几个 *** 作原语:

 CreateSemaphore() 创建一个信号量

 OpenSemaphore() 打开一个信号量

 ReleaseSemaphore() 释放信号量

 WaitForSingleObject() 等待信号量

//信号量句柄

HANDLE global_Semephore;

// 共享资源

char global_Array[256];

void InitializeArray()

{

for(int i = 0;i<256;i++)

{

global_Array[i]=I;

}

}

//线程1

UINT Global_ThreadOne(LPVOID pParam)

{

CEdit ptr=(CEdit )pParam;

ptr->SetWindowText("");

//等待对共享资源请求被通过 等于 P *** 作

WaitForSingleObject(global_Semephore, INFINITE);

for(int i = 0;i<256;i++)

{

global_Array[i]=O;

ptr->SetWindowText(global_Array);

Sleep(10);

}

//释放共享资源 等于 V *** 作

ReleaseSemaphore(global_Semephore, 1, NULL);

return 0;

}

UINT Global_ThreadTwo(LPVOID pParam)

{

CEdit ptr=(CEdit )pParam;

ptr->SetWindowText("");

WaitForSingleObject(global_Semephore, INFINITE);

for(int i = 0;i<256;i++)

{

global_Array[i]=T;

ptr->SetWindowText(global_Array);

Sleep(10);

}

ReleaseSemaphore(global_Semephore, 1, NULL);

return 0;

}

UINT Global_ThreadThree(LPVOID pParam)

{

CEdit ptr=(CEdit )pParam;

ptr->SetWindowText("");

WaitForSingleObject(global_Semephore, INFINITE);

for(int i = 0;i<256;i++)

{

global_Array[i]=H;

ptr->SetWindowText(global_Array);

Sleep(10);

}

ReleaseSemaphore(global_Semephore, 1, NULL);

return 0;

}

void CSemaphoreDlg::OnBnClickedButtonOne()

{

//设置信号量 1 个资源 1同时只可以有一个线程访问

global_Semephore= CreateSemaphore(NULL, 1, 1, NULL);

this->StartThread();

// TODO: Add your control notification handler code here

}

void CSemaphoreDlg::OnBnClickedButtonTwo()

{

//设置信号量 2 个资源 2 同时只可以有两个线程访问

global_Semephore= CreateSemaphore(NULL, 2, 2, NULL);

this->StartThread();

// TODO: Add your control notification handler code here

}

void CSemaphoreDlg::OnBnClickedButtonThree()

{

//设置信号量 3 个资源 3 同时只可以有三个线程访问

global_Semephore= CreateSemaphore(NULL, 3, 3, NULL);

this->StartThread();

// TODO: Add your control notification handler code here

}

信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的>

以上就是关于go 的并发调度(一) GMP 模型全部的内容,包括:go 的并发调度(一) GMP 模型、Java多线程并发 *** 作数据库能否提高运行速度。、问题:什么是数据库的完整性等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/9308829.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-27
下一篇 2023-04-27

发表评论

登录后才能评论

评论列表(0条)

保存