当前位置: 美高梅集团手机版 > 美高梅集团 > 正文

多线程操作过程中往往多个线程是并发执行的,

时间:2019-10-07 05:05来源:美高梅集团
主线程打印10次,子线程打印5次,如此循环20次 线程 在单进程环境下使用多线程执行多个任务。一个进程的所有线程可以访问该进程的资源。当然也需要涉及处理一致性问题。 这个题

主线程打印10次,子线程打印5次,如此循环20次

线程

在单进程环境下使用多线程执行多个任务。一个进程的所有线程可以访问该进程的资源。当然也需要涉及处理一致性问题。

美高梅集团手机版 1

这个题目难点在哪里呢,如何让两个线程交替执行呢,需要用到等待和唤醒

线程标识

进程ID pid_t数据类型表示 ,而线程用 pthread_t 数据类型表示。

美高梅集团手机版 2

image.png

不同平台对pthread_t 实现不同。

说到多线程同步问题就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。比如常见的车票的销售问题。

首先两个线程实现分别打印

线程创建
#include<stdio.h>  
#include<pthread.h>  
#include<string.h>  
#include<sys/types.h>  
#include<unistd.h>  
pthread_t main_tid;  
void print_ids(const char *str)  
{  
    pid_t pid;      //进程id  
    pthread_t tid;  //线程id  
    pid = getpid();       //获取当前进程id  
    tid = pthread_self(); //获取当前线程id  
    printf("%s pid: %u tid: %u (0x%x)/n",  
                str,  
                (unsigned int)pid,  
                (unsigned int)tid,  
                (unsigned int)tid);  
}  
void *func(void *arg)  
{  
    print_ids("new  thread:");  
    return ((void *)0);  
}  
int main()  
{  
    int err;  
    err = pthread_create(&main_tid, NULL, func, NULL); //创建线程  
    if(err != 0){  
        printf("create thread error: %s/n",strerror(err));  
        return 1;  
    }  
    printf("main thread: pid: %u tid: %u (0x%x)/n",   
                (unsigned int)getpid(),  
                (unsigned int)pthread_self(),  
                (unsigned int)pthread_self());  
    print_ids("main thread:");  
    sleep(1);  
    return 0;  
}  

运行 gcc -Wall -o pthread_create pthread_create.c -lpthread
需要链接 pthread 库文件
注意:主线程需要休眠,不然可能会退出导致新线程退出。二是使用pthread_self()函数获取线程ID,而不是用main_tid,用可以用,但可能会出问题,因为新线程在主线程调用pthread_creat返回之前就运行的话,main_tid可能是未初始化的值。值得注意。

线程同步

所谓线程同步就是为了防止多个线程抢夺同一个资源造成的数据安全问题,所采取的一种措施。主要的方法有以下几种:

  • 互斥锁

使用@synchronized解决线程同步问题相比较NSLock要简单一些,但是效率是众多锁中最差的。首先选择一个对象作为同步对象,然后将”加锁代码”(争夺资源的读取、修改代码)放到代码块中。 注意:锁定1份代码只用1把锁,用多把锁是无效的。使用互斥锁,在同一个时间,只允许一条线程执行锁中的代码.因为互斥锁的代价非常昂贵,所以锁定的代码范围应该尽可能小,只要锁住资源读写部分的代码即可。使用互斥锁也会影响并发的目的。

 @synchronized { //1.先检查票数 int count = leftTicketsCount; if (count>0) { //暂停一段时间 [NSThread sleepForTimeInterval:0.002]; //2.票数-1 leftTicketsCount= count-1; //获取当前线程 NSThread *current=[NSThread currentThread]; NSLog(@"%@--卖了一张票,还剩余%d张票", current.name, leftTicketsCount); } else { //退出线程 [NSThread exit]; } }
  • 同步锁NSLock

iOS中对于资源抢占的问题可以使用同步锁NSLock来解决,使用时把需要加锁的代码(以后暂时称这段代码为”加锁代码“)放到NSLock的lock和unlock之间。

美高梅集团手机版 3Paste_Image.png

同步锁时如果一个线程A已经加锁,线程B就无法进入。那么B怎么知道是否资源已经被其他线程锁住呢?可以通过tryLock方法,此方法会返回一个BOOL型的值,如果为YES说明获取锁成功,否则失败。

  • 使用GCD解决资源抢占问题

在GCD中提供了一种信号机制,也可以解决资源抢占问题(和同步锁的机制并不一样)。GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”之后就调用信号等待命令开始等待,此时其他线程无法进入,执行完后发送信号通知,其他线程开始进入执行,如此一来就达到了线程同步目的。

 dispatch_semaphore_t _semaphore;//定义一个信号量 #pragma mark 请求图片数据 -requestData:index{ NSData *data; NSString *name; # 信号等待 # 第二个参数:等待时间 dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); if (_imageNames.count>0) { name=[_imageNames lastObject]; [_imageNames removeObject:name]; } //信号通知 dispatch_semaphore_signal(_semaphore); if{ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; }
  • NSCondition 实现控制线程通信

NSCondition 的对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。单纯解决线程同步问题不是NSCondition设计的主要目的,NSCondition更重要的是解决线程之间的调度关系(当然,这个过程中也必须先加锁、解锁)。NSCondition可以调用wati方法控制某个线程处于等待状态,直到其他线程调用signal(此方法唤醒一个线程,如果有多个线程在等待则任意唤醒一个)或者broadcast(此方法会唤醒所有等待线程)方法唤醒该线程才能继续。

 //初始化锁对象 _condition=[[NSCondition alloc]init]; #pragma mark 创建图片 -createImageName{ [_condition lock]; //如果当前已经有图片了则不再创建,线程处于等待状态 if (_imageNames.count>0) { NSLog(@"createImageName wait, current:%i",_currentIndex); [_condition wait]; }else{ NSLog(@"createImageName work, current:%i",_currentIndex); //生产者,每次生产1张图片 [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",_currentIndex++]]; //创建完图片则发出信号唤醒其他等待线程 [_condition signal]; } [_condition unlock]; }

iOS中的其他锁

在iOS开发中,除了同步锁有时候还会用到一些其他锁类型,在此简单介绍一下:

NSRecursiveLock:递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。NSDistributedLock:分布锁,它本身是一个互斥锁,基于文件方式实现锁机制,可以跨进程访问。pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。

有一张图片简单的比较了各种锁的加解锁性能:

美高梅集团手机版 4Paste_Image.png

还有一种方式可以达到线程同步,那就是同步执行

  • 同步执行 :我们可以使用多线程的知识,把多个线程都要执行此段代码添加到同一个串行队列,这样就实现了线程同步的概念。当然这里可以使用 GCD 和 NSOperation 两种方案,我都写出来。

    #GCD#需要一个全局变量queue,要让所有线程的这个操作都加到一个queue中dispatch_sync(queue, ^{ NSInteger ticket = lastTicket; [NSThread sleepForTimeInterval:0.1]; NSLog(@"%ld - %@",ticket, [NSThread currentThread]); ticket -= 1; lastTicket = ticket;});#NSOperation & NSOperationQueue#1. 全局的 NSOperationQueue, 所有的操作添加到同一个queue中# 2. 设置 queue 的 maxConcurrentOperationCount 为 1#3. 如果后续操作需要Block中的结果,就需要调用每个操作的waitUntilFinished,阻塞当前线程,一直等到当前操作完成,才允许执行后面的。waitUntilFinished 要在添加到队列之后!NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{NSInteger ticket = lastTicket;[NSThread sleepForTimeInterval:1];NSLog(@"%ld - %@",ticket, [NSThread currentThread]);ticket -= 1;lastTicket = ticket;}];[queue addOperation:operation];[operation waitUntilFinished];#后续要做的事
    

PS:原子和非原子属性

atomic

的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。比如setter函数里面改变两个成员变量,如果你用nonatomic的话,getter可能会取到只更改了其中一个变量时候的状态,这样取到的东西会有问题。atomic:能够实现“单写多读”的数据保护,同一时间只允许一个线程修改属性值,但是允许多个线程同时读取属性值,在多线程读取数据时,有可能出现“脏”数据

读取的数据可能会不正确。原子属性是默认属性,atomic在setter方法内部加了一把自旋锁如果不需要考虑线程安全,要指定 nonatomic。

关于atomic的实现最开始的方式如下,我们可以看到其实现原理也是通过加锁实现的。

- setCurrentImage:(UIImage *)currentImage{ @synchronized { if (_currentImage != currentImage) { [_currentImage release]; _currentImage = [currentImage retain]; // do something } }}- (UIImage *)currentImage{ @synchronized { return _currentImage; }}

美高梅集团手机版 5image.png美高梅集团手机版 6image.png

线程终止和线程等待

美高梅集团手机版 7

image.png

美高梅集团手机版 8

image.png

美高梅集团手机版,线程使用一般的 return ((void *) 1) 或者 pthread_exit((void *) 1),即退出状态码,在其他线程中可以通过 pthread_join函数获得该线程的退出码。

美高梅集团手机版 9

image.png

线程间通信

线程间通信用到的比较多的包括俩个方面: 其他线程向主线程的通信,其他俩个线程间的通信。

  • 从其他线程回到主线程的方法我们都知道在其他线程操作完成后必须到主线程更新UI。所以,介绍完所有的多线程方案后,我们来看看有哪些方法可以回到主线程。

    #NSThread[self performSelectorOnMainThread:@selector withObject:nil waitUntilDone:NO];#GCD dispatch_async(dispatch_get_main_queue;#NSOperationQueue[[NSOperationQueue mainQueue] addOperationWithBlock:^{}];
    
  • 线程间通信

    线程间通信和进程间通信从本质上讲是相似的。线程间通信就是在进程内的两个执行流之间进行数据的传递,就像两条并行的河流之间挖出了一道单向流动长沟,使得一条河流中的水可以流入另一条河流,物质得到了传递。

    A. performSelect On The Thread

    框架为我们提供了强制在某个线程中执行方法的途径,如果两个非主线程的线程需要相互间通信,可以先将自己的当前线程对象注册到某个全局的对象中去,这样相 互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了,由于主线程比较特殊,所以框架直接提供了在出线程执行的方法。

    #在主线程上执行操作,例如给UIImageVIew设置图片- performSelectorOnMainThread:aSelector withObject:arg waitUntilDone:wait //在指定线程上执行操作- performSelector:aSelector onThread:(NSThread *)thread withObject:arg waitUntilDone:wait #在分线程中下载完图片后通知主线程更新 UI,通过如下方法,传递参数。[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
    

    B.Mach Port在苹果的Thread Programming Guide的Run Pool一节的Configuring a Port-Based Input Source 这一段中就有使用Mach Port进行线程间通信的例子。其实质就是父线程创建一个NSMachPort对象,在创建子线程的时候以参数的方式将其传递给子线程,这样子线程中就可以向这个传过来的 NSMachPort对象发送消息,如果想让父线程也可以向子线程发消息的话,那么子线程可以先向父线程发个特殊的消息,传过来的是自己创建的另一个 NSMachPort对象,这样父线程便持有了子线程创建的port对象了,可以向这个子线程的port对象发送消息了。当然各自的port对象需要设置delegate以及schdule到自己所在线程的RunLoop中,这样来了消息之后,处理port消息的delegate方法会被调用,你就可以自己处理消息了。

    下面是一处使用源码:

    #define kMsg1 100#define kMsg2 101- viewDidLoad {[super viewDidLoad];//1. 创建主线程的port // 子线程通过此端口发送消息给主线程NSPort *myPort = [NSMachPort port];//2. 设置port的代理回调对象myPort.delegate = self;//3. 把port加入runloop,接收port消息[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];NSLog(@"---myport %@", myPort);//4. 启动次线程,并传入主线程的portMyWorkerClass *work = [[MyWorkerClass alloc] init];[NSThread detachNewThreadSelector:@selector(launchThreadWithPort:) toTarget:work withObject:myPort];}- handlePortMessage:(NSMessagePort*)message{NSLog(@"接到子线程传递的消息!%@",message);//1. 消息idNSUInteger msgId = [[message valueForKeyPath:@"msgid"] integerValue];//2. 当前主线程的portNSPort *localPort = [message valueForKeyPath:@"localPort"];//3. 接收到消息的portNSPort *remotePort = [message valueForKeyPath:@"remotePort"];if (msgId == kMsg1){ //向子线的port发送消息 [remotePort sendBeforeDate:[NSDate date] msgid:kMsg2 components:nil from:localPort reserved:0];} else if (msgId == kMsg2){ NSLog(@"操作2....n"); }}
    

MyWorkerClass

#import "MyWorkerClass.h"@interface MyWorkerClass() <NSMachPortDelegate> { NSPort *remotePort; NSPort *myPort; }@end#define kMsg1 100#define kMsg2 101@implementation MyWorkerClass- launchThreadWithPort:port { @autoreleasepool { //1. 保存主线程传入的port remotePort = port; //2. 设置子线程名字 [[NSThread currentThread] setName:@"MyWorkerClassThread"]; //3. 开启runloop [[NSRunLoop currentRunLoop] run]; //4. 创建自己port myPort = [NSPort port]; //5. myPort.delegate = self; //6. 将自己的port添加到runloop //作用1、防止runloop执行完毕之后推出 //作用2、接收主线程发送过来的port消息 [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode]; //7. 完成向主线程port发送消息 [self sendPortMessage]; }}/** * 完成向主线程发送port消息 */- sendPortMessage { NSMutableArray *array =[[NSMutableArray alloc]initWithArray:@[@"1",@"2"]];//发送消息到主线程,操作1[remotePort sendBeforeDate:[NSDate date] msgid:kMsg1 components:array from:myPort reserved:0]; //发送消息到主线程,操作2 // [remotePort sendBeforeDate:[NSDate date] // msgid:kMsg2 // components:nil // from:myPort // reserved:0];}#pragma mark - NSPortDelegate/** * 接收到主线程port消息 */- handlePortMessage:(NSPortMessage *)message{ NSLog(@"接收到父线程的消息...n");// unsigned int msgid = [message msgid];// NSPort* distantPort = nil;//// if (msgid == kCheckinMessage)// {// distantPort = [message sendPort];//// }// else if(msgid == kExitMessage)// {// CFRunLoopStop((__bridge CFRunLoopRef)[NSRunLoop currentRunLoop]);// }}@end

另外Notification在多线程中的使用需要注意

Notification在多线程中只在同一个线程中POST和接收到消息,如果想实现,在一个线程中发通知,在另一个线程中接收到事件,需要用到通知的 重定向技术,这其中用到了进程中的通信。了解更多看这里Notification与多线程。

本文参考文章:IOS多线程开发其实很简单iOS线程通信和进程通信的例子(NSMachPort和NSTask,NSPipe)

循环不能好好执行,当进入for循环打印语句的时候,线程被打断了,没有完整的执行一次for循环,比如主线程8趟,刚执行就被子线程11趟打断了,那么如何让这一次for循环完美执行呢

线程取消

美高梅集团手机版 10

image.png

pthread_cancel( ID )仅仅是提出要求,该线程并不等待。

线程可以安排退出时需要调用的函数,称为线程清理处理程序,记录程序记录在栈中,执行顺序和注册顺序相反。

美高梅集团手机版 11

image.png

美高梅集团手机版 12

image.png

编辑:美高梅集团 本文来源:多线程操作过程中往往多个线程是并发执行的,

关键词: