4:一个经典的多线程同步问题汇总
多线程同步的几种方法

多线程同步的几种方法
多线程同步的几种方法主要包括临界区、互斥量、信号量、事件和读写锁等。
这些方法可以有效地控制多个线程对共享资源的访问,避免出现数据不一致和线程冲突的问题。
1.临界区:通过临界区实现多个线程对某一公共资源或一段代码的串行访问,可以保证某一时刻只有一个线程访问某一资源,速度快,适合控制数据的访问。
2.互斥量:互斥量是最简单的同步机制,即互斥锁。
多个进程(线程)均可以访问到一个互斥量,通过对互斥量加锁,从而来保护一个临界区,防止其它进程(线程)同时进入临界区,保护临界资源互斥访问。
3.信号量:信号量可以控制有限用户对同一资源的的访问而设计。
4.事件:通过通知线程的有一些事件已经发生,从而可以启动后续的任务执行。
5.读写锁:读写锁适合于使用在读操作多、写操作少的情况,比如数据库。
读写锁读锁可以同时加很多,但是写锁是互斥的。
当有进程或者线程要写时,必须等待所有的读进程或者线程都释放自己的读锁方可以写。
数据库很多时候可能只是做一些查询。
以上信息仅供参考,如有需要,建议咨询专业编程技术
人员。
多线程编程技术实现经典进程同步问题

Re l e Cls ia o e sS n h O ia i n a i a sc l z Pr c s y c r n z t 0 P o lm r u h M u t ‘ t r a e c n q e r b e Th o g li_ h e d d Te h i u s 。
读者 一写者问题的读写操作限制( 包括读者优先和写
者优先) :
撰写多线程程序的一个最具挑战性的问题就是 : 如何
让一个线程和另一个线程合作l 。这引出了一个非常重 2 J
要的问题 : 同步。所谓 同步是指进程、 线程间相互通信时
避免破坏各 自数据 的能力。Wi o s n w 环境下的同步问题 d 是由Wi 2系统的 C U时间片分配方式引起的。虽然在 n 3 P 某一时刻, 只有一个线程 占用 C U( C u) P 单 P 时间 , 但是无
Ke o d : lt— tra e ; rd c r u tme ;ed r — i ; inn hlsp es yw r s mu i h e d — d p o u e .c s o r r e —wr e dn i p i o h r a t r g o
O 引 言 多线程的应用广泛, 作用也很大, 如在工 业 自动化 例 控制、 数据的后台查询 、 图形处理等方面。在单线程 的应
WANG e —e, W n liXU n —o g Tig r n ( ol e f o ue S i c n eh o g ,oco i rt , uhu2 5 0 , hn ) C lg mptr c neadT cnl y Soh w Unv sy S zo 1 06 C ia e oC e o ei
.
多线 程编程技术 实现 经典 进程 同步 问题
多线程同步的五种方法

多线程同步的五种⽅法⼀、为什么要线程同步因为当我们有多个线程要同时访问⼀个变量或对象时,如果这些线程中既有读⼜有写操作时,就会导致变量值或对象的状态出现混乱,从⽽导致程序异常。
举个例⼦,如果⼀个银⾏账户同时被两个线程操作,⼀个取100块,⼀个存钱100块。
假设账户原本有0块,如果取钱线程和存钱线程同时发⽣,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。
因此多线程同步就是要解决这个问题。
⼆、不同步时的代码Bank.javapackage threadTest;/*** @author lixiaoxi**/public class Bank {private int count =0;//账户余额//存钱public void addMoney(int money){count +=money;System.out.println(System.currentTimeMillis()+"存进:"+money);}//取钱public void subMoney(int money){if(count-money < 0){System.out.println("余额不⾜");return;}count -=money;System.out.println(+System.currentTimeMillis()+"取出:"+money);}//查询public void lookMoney(){System.out.println("账户余额:"+count);}}SyncThreadTest.javapackage threadTest;public class SyncThreadTest {public static void main(String args[]){final Bank bank=new Bank();Thread tadd=new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubwhile(true){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}bank.addMoney(100);bank.lookMoney();System.out.println("\n");}}});Thread tsub = new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubwhile(true){bank.subMoney(100);bank.lookMoney();System.out.println("\n");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}});tsub.start();tadd.start();}}代码很简单,我就不解释了,看看运⾏结果怎样呢?截取了其中的⼀部分,是不是很乱,有些看不懂。
【麦子学院】iOS开发多线程同步问题

【麦子学院】iOS开发多线程同步问题在iOS开发中,我们经常会遇到多线程问题,可能说到多线程,大家马上就会想到NSLock、NSCondtion甚至OSSpinLock等等各种线程锁。
但今天我们不谈线程锁,今天只和大家聊聊iOS开发多线程同步的问题。
线程同步和线程锁不同,要实现同步问题,首先要清楚为什么需要线程同步?线程不同步主要原因在于多个线程可能同时操作某个对象从而导致状态不一致的问题。
是不是可以这么理解,如果多线程不会同一时刻访问对象就解决了同步问题。
如何做到这一点?可以采用串行队列的思想,何为串行队列?可以简单理解为所有操作都必须按顺序依次执行。
主线程就是串行队列,最简单的同步方式就是把同步操作放到主线程执行,然并卵。
进入正题,下面我们来看看具体的两种线程同步方法吧:GCD同步GCD是创建子线程最简单的方式之一,既然要实现线程同步,首先需要创建串行队列:_queue = dispatch_queue_create("com.olinone.synchronize.serialQueue", NULL); dispatch_queue_t dQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);dispatch_set_target_queue(_queue, dQueue);然后,将所有同步事件依次加入队列中即可确保多线程同步dispatch_async(_queue, ^{block();});NSOperationQueue同步通过GCD的方式即可满足绝大多数需求,但是也难免有个别特殊需求,比如操作的取消。
此时,NSOperationQueue将是不错的选择NSOperationQueue虽然是并发多线程池,但是巧妙的设计也可以让其实现串行队列的功能。
当maxConcurrentOperationCount=1的时候,同一时刻只有一个NSOperation被执行,NSOperationQueue就由并发执行变成串行执行NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; operationQueue.maxConcurrentOperationCount = 1;实现方式与GCD一样,依次将同步操作加入到线程池中即可实现同步操作的串行执行- (void)execSyncBlock:(void (^)())block {if (NSOperationQueue.currentQueue == self) {block();} else {NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:block]; [self addOperations:@[operation] waitUntilFinished:YES];}}HJSynchronizeDemo详细展示了实际使用方式。
C++线程同步的四种方式(Windows)

C++线程同步的四种方式(Windows)为什么要进行线程同步?在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。
更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。
正常情况下对这种处理结果的了解应当在其处理任务完成后进行。
如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。
例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。
如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。
这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。
代码示例:两个线程同时对一个全局变量进行加操作,演示了多线程资源访问冲突的情况。
#include "stdafx.h"#include<windows.h>#include<iostream>using namespace std;int number = 1;unsigned long __stdcall ThreadProc1(void* lp){while (number < 100){cout << "thread 1 :"<<number << endl;++number;_sleep(100);}return0;}unsigned long __stdcall ThreadProc2(void* lp){while (number < 100){cout << "thread 2 :"<<number << endl;++number;_sleep(100);}return0;}int main(){CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL); CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);Sleep(10*1000);system("pause");return0;}运行结果:可以看到有时两个线程计算的值相同,不是我们想要的结果。
多线程同步与互斥 方法

多线程同步与互斥方法
多线程同步和互斥是为了保证多个线程能够安全地访问共享资源而采取的措施。
下面是几种常见的多线程同步与互斥的方法:
1. 锁(lock):通过加锁的方式来保护临界区,只有获得锁的线程才能进入临界区执行代码,其他线程需要等待锁的释放。
常见的锁包括互斥锁(mutex)和读写锁(read-write lock)。
2. 信号量(semaphore):允许多个线程同时访问某个资源,但要限制同时访问的线程数量,通过信号量进行计数来实现。
3. 条件变量(condition variable):允许线程在某个条件满足时等待,直到其他线程发出信号通知它们继续执行。
4. 互斥量(mutex):一种特殊的锁,用于确保某段代码只能被一个线程执行,其他线程需要等待。
5. 读写锁(read-write lock):允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
6. 自旋锁(spin lock):不会引起线程的阻塞,在尝试获取锁时,会一直处于循环中直到获取到锁为止。
7. 可重入锁(reentrant lock):允许同一线程多次获取同一个锁而不会发生死锁。
以上方法都是为了解决多线程之间的冲突和竞争条件问题,保证线程安全和数据一致性。
根据具体的场景和需求,选择适合的同步与互斥方法可以提高多线程程序的性能和正确性。
多线程同步的方法

多线程同步的方法
多线程同步的方法有以下几种:
1. 互斥锁:通过互斥锁来保证在同一时间只有一个线程能够访问共享资源,其他线程需要等待锁释放后才能访问。
常见的互斥锁有互斥量(Mutex)和二进制信号量(semaphore)。
2. 条件变量:通过条件变量可以使线程在满足特定条件之前等待,同时其他线程可以通过发送信号来通知等待的线程条件已满足,从而唤醒它们继续执行。
3. 信号量:通过信号量可以控制多个线程对共享资源的访问数量。
它可以用来实现资源的有限共享,通过调用信号量的P
操作(减少信号量的值)和V操作(增加信号量的值)来控制资源的获取和释放。
4. 读写锁:读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
当有线程正在写入时,其他线程无法读取和写入,当有线程正在读取时,其他线程可以读取但不允许写入。
5. 屏障(Barrier):屏障可以使多个线程在某一点上等待,直到所有线程都到达这一点后才继续执行。
屏障常用于多阶段任务中,可以将任务拆分成多个阶段并在每个阶段设置屏障,确保所有线程完成当前阶段后再执行下一阶段。
需要根据具体的场景选择适合的同步方法,以确保线程之间的数据安全和正确的执行顺序。
多线程考试试题含答案

多线程考试试题含答案一、单选题(每题2分,共10分)1.在Java中,哪个类是用来创建线程的?A.ThreadB.RunnableC.CallableD.ExecutorService答案:A2.线程的优先级范围是多少?A.1-10B.0-5C.1-5D.0-10答案:A3.线程的生命周期中,哪个状态表示线程正在执行?A.新建(New)B.可运行(Runnable)C.阻塞(Blocked)D.死亡(Terminated)答案:B4.在Java中,哪个方法是用于启动线程的?A.start()B.run()C.join()D.sleep()答案:A5.线程同步可以使用哪种关键字?A.finalB.volatileC.synchronizedD.transient答案:C二、多选题(每题3分,共15分)1.以下哪些是线程间通信的方式?A.wait()和notify()B.wait()和notifyAll()C.join()D.共享资源答案:ABCD2.在Java中,哪些是创建线程的正确方式?A.继承Thread类B.实现Runnable接口C.实现Callable接口D.使用ExecutorService答案:ABCD3.线程的哪些状态表示线程不在运行?A.可运行(Runnable)B.阻塞(Blocked)C.等待(Waiting)D.睡眠(Sleeping)答案:BCD4.以下哪些是线程池的类型?A.固定大小的线程池B.可缓存的线程池C.单线程执行器D.定时以及周期性执行的线程池答案:ABCD5.线程安全问题通常发生在哪些情况下?A.多个线程访问同一个资源B.一个线程在执行过程中被中断C.多个线程对共享资源进行写操作D.线程执行顺序不确定答案:ACD三、判断题(每题2分,共10分)1.线程一旦启动,就无法停止。
(对/错)答案:对2.线程的sleep()方法可以响应中断。
(对/错)答案:错3.线程的优先级可以随意设置,不受系统限制。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一个经典的多线程同步问题 程序描述: 主线程启动10个子线程并将表示子线程序号的变量地址作为参数传递给子线程。子线程接收参数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出参数和全局变量。 要求: 1.子线程输出的线程序号不能重复。 2.全局变量的输出必须递增。 下面画了个简单的示意图:
分析下这个问题的考察点,主要考察点有二个: 1.主线程创建子线程并传入一个指向变量地址的指针作参数,由于线程启动须要花费一定的时间,所以在子线程根据这个指针访问并保存数据前,主线程应等待子线程保存完毕后才能改动该参数并启动下一个线程。这涉及到主线程与子线程之间的同步。 2.子线程之间会互斥的改动和输出全局变量。要求全局变量的输出必须递增。这涉及到各子线程间的互斥。
下面列出这个程序的基本框架,可以在此代码基础上进行修改和验证。 //经典线程同步互斥问题 #include #include #include
long g_nNum; //全局资源 unsigned int __stdcall Fun(void *pPM); //线程函数 const int THREAD_NUM = 10; //子线程个数
int main() { g_nNum = 0; HANDLE handle[THREAD_NUM]; int i = 0; while (i < THREAD_NUM) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL); i++;//等子线程接收到参数时主线程可能改变了这个i的值 } //保证子线程已全部运行结束 WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE); return 0; }
unsigned int __stdcall Fun(void *pPM) { //由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来 int nThreadNum = *(int *)pPM; //子线程获取参数 Sleep(50);//some work should to do g_nNum++; //处理全局资源 Sleep(0);//some work should to do printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum); return 0; }
运行结果: 可以看出,运行结果完全是混乱和不可预知的。运用Windows平台下各种手段包括关键段,事件,互斥量,信号量等等来解决这个问题。
关键段CRITICAL_SECTION 首先介绍下如何使用关键段,然后再深层次的分析下关键段的实现机制与原理。 关键段CRITICAL_SECTION一共就四个函数,使用很是方便。下面是这四个函数的原型和使用说明。
函数功能:初始化 函数原型: void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 函数说明:定义关键段变量后必须先初始化。
函数功能:销毁 函数原型: void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 函数说明:用完之后记得销毁。
函数功能:进入关键区域 函数原型: void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 函数说明:系统保证各线程互斥的进入关键区域。
函数功能:离开关关键区域 函数原型: void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
然后在经典多线程问题中设置二个关键区域。一个是主线程在递增子线程序号时,另一个是各子线程互斥的访问输出全局资源时。详见代码:
#include #include #include long g_nNum; unsigned int __stdcall Fun(void *pPM); const int THREAD_NUM = 10;
//关键段变量声明 CRITICAL_SECTION g_csThreadParameter, g_csThreadCode;
int main() { printf("经典线程同步--关键段\n");
//关键段初始化 InitializeCriticalSection(&g_csThreadParameter); InitializeCriticalSection(&g_csThreadCode);
HANDLE handle[THREAD_NUM]; g_nNum = 0; int i = 0; while (i < THREAD_NUM) { EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域 handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL); ++i; } WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
DeleteCriticalSection(&g_csThreadCode); DeleteCriticalSection(&g_csThreadParameter); return 0; }
unsigned int __stdcall Fun(void *pPM) { int nThreadNum = *(int *)pPM; LeaveCriticalSection(&g_csThreadParameter);//离开子线程序号关键区域
Sleep(50);//some work should to do EnterCriticalSection(&g_csThreadCode);//进入各子线程互斥区域 g_nNum++; Sleep(0);//some work should to do printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum); LeaveCriticalSection(&g_csThreadCode);//离开各子线程互斥区域 return 0; } 运行结果:
可以看出来,各子线程已经可以互斥的访问与输出全局资源了,但主线程与子线程之间的同步还是有点问题。这是为什么了? 要解开这个迷,最直接的方法就是先在程序中加上断点来查看程序的运行流程。断点处置示意如下: 然后按F5进行调试,正常来说这两个断点应该是依次轮流执行,但实际调试时却发现不是如此,主线程可以多次通过第一个断点即 EnterCriticalSection(&g_csThreadParameter);//进入子线程序号关键区域 这一语句。这说明主线程能多次进入这个关键区域!找到主线程和子线程没能同步的原因后,下面就来分析下原因的原因吧 先找到关键段CRITICAL_SECTION的定义吧,它在WinBase.h中被定义成RTL_CRITICAL_SECTION。而RTL_CRITICAL_SECTION在WinNT.h中声明,它其实是个结构体: typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUGDebugInfo; LONG LockCount; LONG RecursionCount; HANDLE OwningThread; // from the thread's ClientId->UniqueThread HANDLE LockSemaphore; DWORD SpinCount; } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION; 各个参数的解释如下: 第一个参数:PRTL_CRITICAL_SECTION_DEBUGDebugInfo; 调试用的。
第二个参数:LONG LockCount; 初始化为-1,n表示有n个线程在等待。
第三个参数:LONG RecursionCount; 表示该关键段的拥有线程对此资源获得关键段次数,初为0。
第四个参数:HANDLE OwningThread; 即拥有该关键段的线程句柄,微软对其注释为——from the thread's ClientId->UniqueThread
第五个参数:HANDLELockSemaphore; 实际上是一个自复位事件。
第六个参数:DWORDSpinCount; 旋转锁的设置,单CPU下忽略
由这个结构可以知道关键段会记录拥有该关键段的线程句柄即关键段是有“线程所有
权”概念的。事实上它会用第四个参数OwningThread来记录获准进入关键区域的线程句
柄,如果这个线程再次进入,EnterCriticalSection()会更新第三个参数RecursionCount以记录该线程进入的次数并立即返回让该线程进入。其它线程调用EnterCriticalSection()则会被切换到等待状态,一旦拥有线程所有权的线程调用LeaveCriticalSection()使其进入的次数为0