系列文章目录

GCD学习笔记<一>
GCD学习笔记<二>
GCD学习笔记<三>

写在前言:最近忙着面试,正好复习GCD和操作系统,于是续写前面这个系列的文章,本篇主要是讲GCD中的信号量。

Dispatch Semaphore

既然是多线程,就肯定离不开一个关键的东西——Semaphore(信号量)
信号量是一个特殊的变量,程序只能在临界区访问它,也就是说只能对它进行原子操作,而且只允许对它进行请求(P操作)和释放(V操作)。信号量的存在是为了保证多线程资源请求的有序性。不理解的自行复习操作系统。
在GCD中,我们用dispatch_semaphore_t来表示信号量,计数为0时等待,计数大于等于1时则无需等待,将信号量减1后直接进行操作。我们使用dispatch_semaphore_create来产生一个Dispatch Semaphore,该函数的参数即为信号量的初始值。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

P操作即为:

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait是等待信号量大于或等于1,然后对其进行减1,并执行之后的操作,这即为我们常说的P操作。
当然对应的还有一个V操作,dispatch_semaphore_signal,该函数则对信号量进行加1操作,

dispatch_semaphore_signal(semaphore);

有了Dispatch Semaphore,我们便可以解决很多问题,随便举一个当初学操作系统PV操作时的经典的例子读写者问题。
在下面这个例子中,互斥锁其实就是一个特殊的只有两个值的信号量(0/1),这里不想再去介绍互斥锁,所以我直接使用信号量来代替了互斥锁。

问题是这样的:

如果读者来:

  • 无读者、写者,新读者可以读
  • 有写者等待,但有其它读者正在读,则新读者也可以读
  • 有写者写,则新读者等待

如果写者来:

  • 无读者,则新写者可以写
  • 有读者,则新写者等待
  • 有其它写者,则新写者等待

以下是源代码:

#import <Foundation/Foundation.h>

void gcd_write();
void gcd_read();


static dispatch_semaphore_t writable_mutex; //允许写
static int reader_count; // 正在读的进程数,即读者数
static dispatch_semaphore_t read_mutex; // 对 reader_count 的互斥操作


int main(int argc, const char * argv[]) {

    writable_mutex = dispatch_semaphore_create(1);
    reader_count = 0;
    read_mutex = dispatch_semaphore_create(1);


    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();


    dispatch_group_async(group, queue, ^{
        gcd_write();
    });

    dispatch_group_async(group, queue, ^{
        gcd_read();
    });

    dispatch_group_async(group, queue, ^{
        gcd_read();
    });

    dispatch_group_async(group, queue, ^{
        gcd_write();
    });

    dispatch_group_async(group, queue, ^{
        gcd_read();
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    return 0;
}


void gcd_write() {
    dispatch_semaphore_wait(writable_mutex, DISPATCH_TIME_FOREVER);
    NSLog(@"我写了");
    dispatch_semaphore_signal(writable_mutex);
}


void gcd_read() {
    // 先要求锁定读者
    dispatch_semaphore_wait(read_mutex, DISPATCH_TIME_FOREVER);
    if (reader_count == 0) {
        // 请求可读
        dispatch_semaphore_wait(writable_mutex, DISPATCH_TIME_FOREVER);
    }
    ++reader_count;
    NSLog(@"有新读者了,当前读者数 %d",reader_count);
    dispatch_semaphore_signal(read_mutex);
    NSLog(@"我读了");

    dispatch_semaphore_wait(read_mutex, DISPATCH_TIME_FOREVER);
    --reader_count;
    if (reader_count == 0) {
        dispatch_semaphore_signal(writable_mutex);
    }
    NSLog(@"读完了,当前读者数 %d",reader_count);
    dispatch_semaphore_signal(read_mutex);
}

运行结果如下:

当然,由于是并发执行的,所以每次运行的结果并不完全一致,但是都能完全运行完直到done,也没有出现死锁。这个运行结果是我运行多次特意选出一个存在并发读的情况的。