ios基础篇(十一)—— 同步/异步、进程/线程、pthread、线程状态、线程同步、自动释放池

多线程

一、同步/异步

1、1同步

  • 我们之前写程序的时候都是从上到下,从左到右,代码执行顺序
  • 1个人执行多个任务,也是依次执行,1个人同一时间执行1个任务

1.2异步

多个人可以同时执行多个任务

二、进程/线程

2.1进程

  • 进程是指在系统中正在运行的一个应用程序
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内
  • 通过“活动监视器”可以查看Mac系统中所开启的进程

2.2线程

  • 1个进程有多个线程组成(1个进程至少要有1个线程)
  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行

三、多线程执行原理

1个进程中可以开启多个线程,多个线程可以“同时”执行不同的任务
进程-公司,线程-员工,老板-主线程
多线程可以解决程序阻塞的问题
多线程可以提高程序的执行效率

  • 单任务操作系统  只有进程,没有线程  只能干一件事
  • 多任务操作系统  同时

    a.    (单核CPU)同一时间,cpu只能处理1个线程,只有1个线程在执行
    b.    多线程同时执行:是CPU快速的在多个线程之间的切换
    c.    cpu调度线程的时间足够快,就造成了多线程的"同时"执行
    d.    如果线程数非常多,cpu会在n个线程之间切换,消耗大量的cpu资源
    i.    每个线程被调度的次数会降低,线程的执行效率降低

3.1 多线程优/缺点

优点

  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率(cpu,内存)
  • 线程上的任务执行完成后,线程会自动销毁

缺点

  • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占512KB)
  • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 线程越多,cpu在调用线程上的开销就越大
  • 程序设计更加复杂,比如线程间的通信、多线程的数据共享

四、主线程

  • 一个程序运行后,默认会开启1个线程,称为“主线程”或“UI线程”
  • 主线程一般用来  刷新UI界面 ,处理UI事件(比如:点击、滚动、拖拽等事件)
  • 主线程使用注意
  • 别将耗时的操作放到主线程中
  • 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验

五、iOS中多线程的技术方案

POSIX 表示可移植操作系统接口(Portable Operating System Interface )-----pthread

六、pthread演示

导入头文件

#import <pthread.h>

代码

 //线程编号的地址
    pthread_t pthread;
    //第一个参数 线程编号的地址
    //第二个参数 线程的属性
    //第三个参数 线程要执行的方法
        //void *   (*)   (void *)
        //函数的返回值类型  void *    int *指向int的指针  void * 指向任意类型的指针    类似于oc中的id
        //函数的名称  函数指针
        //函数的参数  void *
    //第四个参数 线程要执行的方法的 参数
    
    //方法的返回值  0 成功 其它失败
    int result =  pthread_create(&pthread, NULL, demo, NULL);
方式1
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:nil];
[thread start];
方式2
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:nil];
方式3
    [self performSelectorInBackground:@selector(demo:) withObject:nil];

七、线程的状态

NSThread *thread = [[NSThread alloc] initWithTarget:self 
                                           selector:@selector(demo) object:nil];
[thread start];

- (void)viewDidLoad {
    [super viewDidLoad];
    //新建状态
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    //就绪状态
    [thread start];
}
- (void)demo {
    for (int i = 0; i < 20; i++) {
        NSLog(@"%d",i);       
        if (i == 5) {
            //阻塞状态
            [NSThread sleepForTimeInterval:3];
        }
        if (i == 10) {
            //线程退出    死亡状态
            [NSThread exit];
        }
    }
}

八、控制线程的状态

启动线程

- (void)start;

线程进入就绪状态,当线程执行完毕后自动进入死亡状态。
暂停(阻塞)线程

+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

线程进入阻塞状态
停止线程

+ (void)exit;

线程进入死亡状态

注意:一旦线程停止(死亡)了,就不能再次开启任务

九、线程的属性

  • 线程名称
    • 设置线程名称可以当线程执行的方法内部出现异常的时候记录 异常和当前线程
  • 线程优先级
    • 内核调度算法在决定该运行哪个线程时,会把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的线程具有更多的运行机会。较高优先级不保证你的线程具体执行的时间,只是相比较低优先级的线程,它更有可能被调度器选择执行而已。

异步下载图片 线程间通信 更新ui的操作应该在主线程上

@interface ViewController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UILabel *lbl;
@end

@implementation ViewController
- (void)loadView {
    //初始化scrollview
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.scrollView.backgroundColor = [UIColor whiteColor];
    self.view = self.scrollView;    
    //初始化imageView
    self.imageView = [[UIImageView alloc] init];
    [self.scrollView addSubview:self.imageView];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
    [thread start];
}
//下载网络图片
- (void)downloadImage {
    //图片的地址
    NSURL *url = [NSURL URLWithString:@"http://img04.tooopen.com/images/20130701/tooopen_20083555.jpg"];
    //下载图片
    NSData *data = [NSData dataWithContentsOfURL:url];
    //把NSData转换成UIImage
    UIImage *img = [UIImage imageWithData:data];
    
    //在主线程上更新UI控件  线程间通信
    //waitUntilDone  值是YES 会等待方法之行完毕,才会执行后续代码
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:img waitUntilDone:YES];
}
- (void)updateUI:(UIImage *)img {
    self.imageView.image = img;
    //让imageview的大小和图片的大小一致
    [self.imageView sizeToFit];   
    //设置scrollView滚动范围
    self.scrollView.contentSize = img.size;
}
@end

十、多线程访问共享资源的问题

  • 共享资源
    • 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
    • 比如多个线程访问同一个对象、同一个变量、同一个文件
  • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

   

       

@interface ViewController ()
//总票数
@property (nonatomic, assign) int ticketsCount;
@property (nonatomic, strong) NSObject *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketsCount = 10;  
    self.obj = [NSObject new];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //模拟买票窗口1
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
    [thread1 start];    
    //模拟买票窗口2
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
    [thread2 start];
}
//线程是不安全的
//模拟卖票的方法
- (void)sellTickets {
    while (YES) {
        //模拟耗时
        [NSThread sleepForTimeInterval:1.0];
        //任意一个对象内部都有一把锁
        //加锁会影响程序的性能
        //互斥锁
        //线程同步
        @synchronized(self.obj) {
            //判断还有没有票
            if (self.ticketsCount > 0) {
                self.ticketsCount = self.ticketsCount - 1;
                NSLog(@"剩余%d张票",self.ticketsCount);
            }else{
                NSLog(@"来晚了,票没了");
                break;
            }
        }
    }
}
//输出两次9
//t1   t=10
//t2   t=10
//t1   t=9   log
//t2   t=9   log

//输出8,9
//t2   t=9
//t1   t=8  log
//t2   log
@end

十一、互斥锁(线程同步)

  • 互斥锁使用

@synchronized(锁对象) { // 需要锁定的代码  }

  • 互斥锁
    • 能有效防止因多线程抢夺资源造成的数据安全问题
  • 相关专业术语:线程同步
    • 线程同步的意思是:多条线程按顺序地执行任务
    • 互斥锁,就是使用了线程同步技术

互斥锁原理

  • 互斥锁原理
    • 每一个对象(NSObject)内部都有一个锁(变量),当有线程要进入synchronized到代码块中会先检查对象的锁是打开还是关闭状态,默认锁是打开状态(1),如果是线程执行到代码块内部 会先上锁(0)。如果锁被关闭,再有线程要执行代码块就先等待,直到锁打开才可以进入。

线程执行到synchronized

  • i. 检查锁状态 如果是开锁状态(1)转到ii  如果上锁(0)转到v
  • ii. 上锁(0)
  • iii.执行代码块
  • iv. 执行完毕 开锁(1)
  • v.线程等待(就绪状态)

加锁后程序执行的效率比不加锁的时候要低,因为要线程要等待锁,但是锁保证了多个线程同时操作全局变量的安全性

十二、原子属性

  • 属性中的修饰符
    • nonatomic  非原子属性
    • atomic   原子属性(线程安全),针对多线程设计的,默认值

                    保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值)

                    atomic  本身就有一把锁(自旋锁)

                    单写多读:单个线程写入,多个线程可以读取

  • nonatomic和atomic对比
    • atomic:线程安全,需要消耗大量的资源
    • nonatomic:非线程安全,适合内存小的移动设备
  • iOS开发的建议
    • 所有属性都声明为nonatomic
    • 尽量避免多线程抢夺同一块资源
    • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
@interface ViewController ()
//总票数
@property (nonatomic, assign) int ticketsCount;
@property (nonatomic, strong) NSObject *obj;
//原子属性是线程安全的  自旋锁
@property (nonatomic, copy) NSString *name;
@end

@implementation ViewController
//当同时重写属性的setter和getter方法,不会自动生成_name 成员变量
//为属性生成对应的成员变量
@synthesize name = _name;
//模拟原子属性
- (NSString *)name {
    return _name;
}
- (void)setName:(NSString *)name {
    @synchronized(self) {
        _name = name;
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketsCount = 10; 
    self.obj = [NSObject new];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //模拟买票窗口1
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
    [thread1 start];    
    //模拟买票窗口2
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
    [thread2 start];
}
//线程是不安全的
//模拟卖票的方法
- (void)sellTickets {   
    while (YES) {
        //模拟耗时
        [NSThread sleepForTimeInterval:1.0];
        //任意一个对象内部都有一把锁
        //加锁会影响程序的性能
        //互斥锁
        //线程同步
        @synchronized(self.obj) {
            //判断还有没有票
            if (self.ticketsCount > 0) {
                self.ticketsCount = self.ticketsCount - 1;
                NSLog(@"剩余%d张票",self.ticketsCount);
            }else{
                NSLog(@"来晚了,票没了");
                break;
            }
        } 
    }
}
//输出两次9
//t1   t=10
//t2   t=10
//t1   t=9   log
//t2   t=9   log

//输出8,9
//t2   t=9
//t1   t=8  log
//t2   log
@end

十三、互斥锁和自旋锁

  • 互斥锁
    • 如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其它线程时间片到打开锁后,线程会被唤醒(执行) 
  • 自旋锁
    • 如果发现有其它线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成 自旋锁更适合执行不耗时的代码 
    • 互斥锁
      • 如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其它线程时间片到打开锁后,线程会被唤醒(执行) 
    • 自旋锁
      • 如果发现有其它线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成 自旋锁更适合执行不耗时的代码 

  • 线程安全
    • 线程同时操作是不安全的,多个线程同时操作一个全局变量
    • 线程安全:在多个线程进行读写操作时,仍然能够保证数据的正确 
  • 主线程(UI线程)
    • 几乎所有UIKit提供的类都是线程不安全的,所有更新UI的操作都在主线程上执行
    • 所有包含€的类都是线程不安全的
@interface ViewController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UILabel *lbl;
@end

@implementation ViewController
- (void)loadView {
    //初始化scrollview
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.scrollView.backgroundColor = [UIColor whiteColor];
    self.view = self.scrollView;    
    //初始化imageView
    self.imageView = [[UIImageView alloc] init];
    [self.scrollView addSubview:self.imageView];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
    [thread start];
}
//下载网络图片
- (void)downloadImage {
    //图片的地址
    NSURL *url = [NSURL URLWithString:@"http://img04.tooopen.com/images/20130701/tooopen_20083555.jpg"];
    //下载图片
    NSData *data = [NSData dataWithContentsOfURL:url];
    //把NSData转换成UIImage
    UIImage *img = [UIImage imageWithData:data];  
    //在主线程上更新UI控件  线程间通信
    //waitUntilDone  值是YES 会等待方法之行完毕,才会执行后续代码
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:img waitUntilDone:YES];
}
- (void)updateUI:(UIImage *)img {
    self.imageView.image = img;
    //让imageview的大小和图片的大小一致
    [self.imageView sizeToFit];  
    //设置scrollView滚动范围
    self.scrollView.contentSize = img.size;
}
@end

十四、weak和strong

  • 什么时候用strong和weak
    • OC对象用strong 
    • 连线的UI控件为什么用weak

controller ==》 view ==》view.subViews ==》imageView  强引用

controller --》imageView          弱引用 

controller --》imageView这个位置换成strong也可以,但是不建议,如果一个对象被多个对象强引用, 

这多个对象中有一个对象忘记释放,那么该对象也不能释放 

十五、自动释放池

  • iOS开发中的内存管理
    • 在iOS开发中,并没有JAVA或C#中的垃圾回收机制
    • 在MRC中对象谁申请,谁释放
    • 使用ARC开发,只是在编译时,编译器会根据代码结构自动添加了retain、release和autorelease
  • 自动释放池
    • 标记为autorelease的对象,会被添加到最近一次创建的自动释放池中
    • 当自动释放池被销毁或耗尽时,会向自动释放池中的所有对象发送release消息
  • 自动释放池是什么时候创建的?又是什么时候销毁的?
  1. 每一次主线程的消息循环开始的时候会先创建自动释放池
  2. 消息循环结束前,会释放自动释放池
  3. 自动释放池被销毁或耗尽时会向池中所有对象发送 release 消息,释放所有 autorelease 的对象
  4. 使用 NSThread 做多线程开发时,需要在线程调度方法中手动添加自动释放池
  • 自动释放池和主线程

//在主线程消息循环开始的时候

//在消息循环开始的时候创建了自动释放池,在消息循环结束的时候倾倒自动释放池

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. If you use the Application Kit, you therefore typically don’t have to create your own pools. If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create “local” autorelease pools to help to minimize the peak memory footprint.

  • 什么时候使用自动释放池
  • If you write a loop that creates many temporary objects.
  • You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.
  • If you spawn a secondary thread.
  • You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects. (See Autorelease Pool Blocks and Threads for details.)

for (int i = 0; i < largeNumber; ++i) {
    NSString *str = @"Hello World";
    str = [str stringByAppendingFormat:@" - %d", i];
    str = [str uppercaseString];
}

内存泄漏

for (int i=0; i < 100000000; i++) {
        @autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"hello %d",i];
        }
    }

十五、属性修饰符

retain strong weak assign copy

字符串用copy?

当给属性赋值NSMutableString,如果用strong修饰,是一个地址的指向,NSMutableString发生变化,我们指向的始终是最新的值。

block为什么用cooy?

block就是一个函数,

//属性修饰符
//retain  mrc中使用
// strong arc中使用
// weak   只有arc下才能用weak
// assign arc和mrc都可以使用
// copy   arc和mrc都可以使用
//1 字符串为什么用copy
//2 block作为属性的时候 为什么要用copy
//3 delegate为什么用weak修饰
@property (nonatomic, copy) void (^myBlock)();
@property (nonatomic, copy) NSString *name;
@property (nonatomic, weak) Person *weakPerson;
@property (nonatomic, assign) Person *assignPerson;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 字符串为什么用copy
//    NSMutableString *str = [NSMutableString string];
//    [str appendString:@"hello"];
//    self.name = [str copy];
//    [str appendString:@"zs"];
//    NSLog(@"%@",self.name);
    
    //2 block 为什么要用copy
    //第一种 block   全局block  __NSGlobalBlock__  存储在代码区
//    void (^demo)() = ^{
//        NSLog(@"aaa");
//    };
//    NSLog(@"%@",demo);
    //第二种block   栈Block  __NSStackBlock__  block内部访问了bloak外部的变量
//    int number = 5;
//    void (^demo)() = ^{
//        NSLog(@"aaa %d",number);
//    };
//    NSLog(@"%@",demo);
    //第三种block   堆block  __NSMallocBlock__
//    int number = 5;
//    void (^demo)() = ^{
//        NSLog(@"aaa %d",number);
//    };
//    NSLog(@"%@",[demo copy]);   
//    [self test];    
//    self.myBlock();
//    self.myBlock();   
    
    //3 delegate为什么用weak修饰
//    self.person = [Person new];
//    self.person.delegate = self; 
    //vc-->person-->delegate-->self(vc)
    
    //4 weak和assign的区别
    self.weakPerson = [Person new];
    self.weakPerson.name = @"zs";
    NSLog(@"weakPerson  %@",self.weakPerson.name);
    
    self.assignPerson = [Person new];
    self.assignPerson.name = @"ls";
    NSLog(@"assignPerson  %@",self.assignPerson.name);
}

//给block属性赋值
- (void)test {
    int n = 5;
    [self setMyBlock:^{
        NSLog(@"%d",n);
    }]; 
    NSLog(@"%@",self.myBlock);
}
1 模拟耗时操作
2 多线程的概念
    2.1 同步/异步
    2.2 进程/线程
    2.3 多线程
3 多线程的原理
     多线程之间是切换执行的
     线程执行完毕后会自动销毁
4 主线程
5 iOS中实现多线程的四种技术方案
6 pthread   __bridge  CF和OC对象转化时只涉及对象类型不涉及对象所有权的转化
7 NSThread 基本使用
8 多线程的状态
9 多线程的属性
10 多线程访问共享资源的问题 (模拟卖票的问题)
11 互斥锁  解决 问题 ---》线程同步
12 自旋锁  --  原子属性 (单写多读)
    原子属性    线程安全   效率低
    非原子属性  线程不安全  效率高
13 互斥锁和自旋锁的区别
14 异步下载图片  线程间通信  更新UI的操作应该在主线程上
15 weak和strong--》自动释放池
16 自动释放池
    自动释放池,在主线程的消息循环开启的时候会创建
    当消息循环结束,倾倒自动释放池
17 什么时候使用自动释放池
    循环内部创建大量的临时对象,在循环内部创建自动释放池
    创建子线程的时候,在线程刚刚开始的位置,创建自动释放池
18 自动释放池的面试题
19 属性修饰符 retain strong weak assign copy

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
同步概念 所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指让两个或多个文件夹里的文件保持一致。等等 而,编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。 线同步 同步即协同步调,按预定的先后次序运行。 线同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。 举例1: 银行存款 5000。柜台,折:取3000;提款机,卡:取 3000。剩余:2000 举例2: 内存中100字节,线程T1欲填入全1, 线程T2欲填入全0。但如果T1执行了50个字节失去cpu,T2执行,会将T1写过的内容覆盖。当T1再次获得cpu继续 从失去cpu的位置向后写入1,当执行结束,内存中的100字节,既不是全1,也不是全0。 产生的现象叫做“与时间有关的错误”(time related)。为了避免这种数据混乱,线程需要同步。 “同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步进程间、信号间等等都需要同步机制。 因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。 数据混乱原因: 1. 资源共享(独享资源则不会) 2. 调度随机(意味着数据访问会出现竞争) 3. 线程间缺乏必要的同步机制。 以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。 所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。 互斥量mutex Linux中提供一把互斥锁mutex(也称之为互斥量)。 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。 资源还是共享的,线程间也还是竞争的, 但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。 但,应注意:同一时刻,只能有一个线程持有该锁。 当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。 所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定。 因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。 主要应用函数: pthread_mutex_init函数 pthread_mutex_destroy函数 pthread_mutex_lock函数 pthread_mutex_trylock函数 pthread_mutex_unlock函数 以上5个函数的返回值都是:成功返回0, 失败返回错误号。 pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。 pthread_mutex_t mutex; 变量mutex只有两种取值1、0。 pthread_mutex_init函数 初始化一个互斥锁(互斥量) ---> 初值可看作1 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参1:传出参数,调用时应传 &mutex restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改 参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性 1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER; 2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL) pthread_mutex_destroy函数 销毁一个互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); pthread_mutex_lock函数 加锁。可理解为将mutex--(或-1) int pthread_mutex_lock(pthread_mutex_t *mutex); pthread_mutex_unlock函数 解锁。可理解为将mutex ++(或+1) int pthread_mutex_unlock(pthread_mutex_t *mutex); pthread_mutex_trylock函数 尝试加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex); 加锁与解锁 lock与unlock: lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。 unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。 例如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直至T1解锁。T1解锁后,T2 T3 T4均被唤醒,并自动再次尝试加锁。 可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++ lock与trylock: lock加锁失败会阻塞,等待锁释放。 trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。 加锁步骤测试: 看如下程序:该程序是非常典型的,由于共享、竞争而没有加任何同步机制,导致产生于时间有关的错误,造成数据混乱: #include #include #include void *tfn(void *arg) { srand(time(NULL)); while (1) { printf("hello "); sleep(rand() % 3); /*模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误*/ printf("world\n"); sleep(rand() % 3); } return NULL; } int main(void) { pthread_t tid; srand(time(NULL)); pthread_create(&tid, NULL, tfn, NULL); while (1) { printf("HELLO "); sleep(rand() % 3); printf("WORLD\n"); sleep(rand() % 3); } pthread_join(tid, NULL); return 0; } 【mutex.c】 【练习】:修改该程序,使用mutex互斥锁进行同步。 1. 定义全局互斥量,初始化init(&m, NULL)互斥量,添加对应的destry 2. 两个线程while中,两次printf前后,分别加lock和unlock 3. 将unlock挪至第二个sleep后,发现交替现象很难出现。 线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠。睡醒解锁后又立即加锁,这两个库函数本身不会阻塞。 所以在这两行代码之间失去cpu的概率很小。因此,另外一个线程很难得到加锁的机会。 4. main 中加flag = 5 将flg在while中-- 这时,主线程输出5次后试图销毁锁,但子线程未将锁释放,无法完成。 5. main 中加pthread_cancel()将子线程取消。 【pthrd_mutex.c】 结论: 在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。 死锁 1. 线程试图对同一个互斥量A加锁两次。 2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁 【作业】:编写程序,实现上述两种死锁现象。 读写锁 与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。 读写锁状态: 一把读写锁具备三种状态: 1. 读模式下加锁状态 (读锁) 2. 写模式下加锁状态 (写锁) 3. 不加锁状态 读写锁特性: 1. 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。 2. 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。 3. 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。 读写锁非常适合于对数据结构读的次数远大于写的情况。 主要应用函数: pthread_rwlock_init函数 pthread_rwlock_destroy函数 pthread_rwlock_rdlock函数 pthread_rwlock_wrlock函数 pthread_rwlock_tryrdlock函数 pthread_rwlock_trywrlock函数 pthread_rwlock_unlock函数 以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。 pthread_rwlock_t类型 用于定义一个读写锁变量。 pthread_rwlock_t rwlock; pthread_rwlock_init函数 初始化一把读写锁 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 参2:attr表读写锁属性,通常使用默认属性,传NULL即可。 pthread_rwlock_destroy函数 销毁一把读写锁 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); pthread_rwlock_rdlock函数 以读方式请求读写锁。(常简称为:请求读锁) int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); pthread_rwlock_wrlock函数 以写方式请求读写锁。(常简称为:请求写锁) int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); pthread_rwlock_unlock函数 解锁 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); pthread_rwlock_tryrdlock函数 非阻塞以读方式请求读写锁(非阻塞请求读锁) int pthread_

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值