线程同步

几个定义:

    1.什么是临界资源?

        在多线程环境下,需要共享的资源,比如打印机这类资源,就是临界资源。

    2.什么是临界区?

        在多线程的环境下,有时候我们会访问一些全局资源(简单的来说就是要和其他线程共享的资源),比如数据库连接。一个进程只有一个数据库连接,在这种情况下这个数据库连接就是共享的资源,也就是临界资源。而访问临界资源的代码就是临界区。

    3.互斥是什么?

        互斥是指某一个资源在某一个时刻只能被一个线程访问,具有唯一性和排他性。

    4.为什么不使用多进程而要使用多线程?

        在多线程操作系统中,线程是调度的实体,而进程是拥有资源的实体。相对于进程来说,线程比较轻量,创建和销毁线程比进程的开销要小得多。 线程之间的通信比较方便,进程之间的通信比较复杂。因为线程之间是共享数据的(文件描述符等资源)。

    5.什么是原子操作?

        原子操作是不可中断的,要么全做,要么全不做。

    6.死锁的四大条件是什么?

        a)互斥:在某一个时刻,一个资源只能被一个线程锁占用,而不能被其他线程占用。
        b)请求和保持条件:考虑这种情况:A获得了资源R1,如果他不需要其他资源,那么就不会造成死锁吧。所以请求和保持条件简单的来说就是A在获取了R1之后,还要在想系统发出请求,请求资源R2。下文会继续谈到这个问题。
        c)不可抢占条件:一个线程拥有的资源不能被其他线程抢占。能抢占话,那还能玩吗?
        d)环路等待条件:继续b条件的讨论。假设这样一种情况:A占用的资源R1,现在想要获取资源R2。B占用R2,现在想要R1。这种情况下,A等待B释放R2,B等待A释放R1,形成了一个封闭的环。这样谁都不能前进,就形成死锁。(想想A如果能抢占B的资源的话,还会死锁吗?)
       

1.互斥锁:

    互斥锁是一种很常见的锁。在互斥锁可以用力啊表示两种状态,要么被锁上了,要么没被锁上。linux环境下,互斥锁定义在pthread.h头文件中,类型为pthread_mutext_t,可以使用PTHREAD_MUTEX_INITIALIZER或者函数pthread_mutex_init()函数来进行初始化。使用了函数pthread_mutex_init()要初始化的互斥锁需要使用函数pthread_mutex_destroy()来释放。用函数pthread_mutex_lock来上锁,pthread_mutex_unlock来解锁。使用互斥锁带来的问题就是可能会发生死锁。上文已经解释过了。

2.读写锁:

    读写锁是一种比互斥锁细粒度更小的锁。读写锁又分为读锁和写锁。 它有这样一种定义:当一个线程对一个资源加上了读锁之后,那么其他贤臣够可以对这个资源继续加上读锁,而不能写锁。当一个线程对一个资源加上了写锁,那么其他线程既不能对这个资源加上写锁,也不能加上读锁。但是加上了读锁不能保证你的数据没有被修改。有的人比较调皮,会在加上了读锁之后,对临界资源进行修改,这就与目的相悖了。因此,很多时候都不用读写锁,而使用互斥锁。而且读写锁的熟读不一定比互斥锁要快。

3.条件变量:

    假设存在这种情况,你的某一个线程,需要等待临界资源X(为整型)变为0的情况,为了能在检查的时候其他县城不能修改这个锁,你给它加上了互斥锁。然后在检查之后,释放锁,给其他线程修改X的机会。为了能够在X到达0的时候执行下去,你加上了一个循环。
    while (true) {
        lock
        check x == 0
        unlock
     }
这种方式称为轮询。采用轮询是很耗时间的。因为进程必须不停的主动获得锁、检查X条件、释放锁、再获得锁、再检查、再释放,一直到满足运行的条件的时候才可以。为了能够避免这种情况情况的发生,就有了条件变量。 当线程X发现被锁定的变量不满足条件时会自动的释放锁并把自身置于等待状态,让出CPU的控制权给其它线程。这个时候其他线程就可以去修改X,当修改完成后在通知那些等待的线程。这一种通知模式的同步方式,大大的减少了CPU的使用。
    同样的条件变量在linux中被定义在头文件phtread.h中,可以只用pthread_cond_init函数或者PTHREAD_COND_INITIALIZER常量去初始化。使用pthread_cond_init()函数初始化的条件变量需要使用pthread_cond_destroy()去初始化。相关的函数在《UNIX 环境高级编程》中有提到。

4.自旋锁:

    自旋锁是一种比较底层的锁。通常用来实现其他的锁。比如linux中的信号量就是基于自旋锁实现的。下面是linux中的信号量结构体的定义:
struct  semaphore {  
    spinlock_t      lock;  // 自旋锁类型
unsigned int count;
struct list_head wait_list;
};
    自旋锁有一个特性,它会一直占用CPU知道它等待的资源被释放。因此,在单核环境下,自旋锁是无效的。而自旋锁的这种特性,使得他可能用来作为其他锁的底层实现。

5.记录锁:

    为了可以增加文件读写的并行性,我们可以将文件的读写动作进一步的细分为对某个范围的读写。比如一个文件的前1K字节可以被读取,在这个时刻,还能在文件的最后一部分执行写操作,只要这两部分不相交。当染在读写这一个特定范围的时候,是需要加锁的,这个锁就是记录锁,也叫文件锁。

注意事项:

下面内容引用了IBM的一篇博文,原文连接: https://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/
    1.使用条件变量的时候需要互斥锁来辅助。
        在linux下面调用pthread_cond_wait函数来等待条件变量返回的时候,需要使用一个互斥锁来避免线程间的竞争和互斥。但是当条件变量返回的时候这个锁还是没有被释放的,在条件变量返回之后,不用对这个互斥锁执行加锁操作,而是要对这个锁执行解锁,才能释放。
    2.Linux下面的条件变量会自动复位。
        假设有这样一种情况,有两个条件变量C1和C2先后被触发,C1和C2触发的都是同一个条件。在C1和C2中间有一个线程阻塞P1(也就是线程P1调用了函数pthread_cond_wait)发生了。你觉得是C1还是C2会作为唤醒P1的条件呢?
        答案是C2。因为在Linux下,通过调用pthread_cond_signal()来释放被阻塞的线程时,无论存不存在被阻塞的线程,条件都将被复位。下一个被条件阻塞的线程将不受影响。简单的来说,就是Linux下面的条件发生的时候,如果没有对这个条件的等待线程,那个这个条件就会被丢弃。
        3.重复加锁的问题。
                 有时候你会想要设计一个函数,它会发生递归。而这个函数中,含有对锁进行加锁和释放锁的操作,这时候就有可能发生对一个锁重复加锁的情况,最直观的表达就是:
                pthread_mutex_lock();
                pthread_mutex_lock();
                上面这个两个函数的执行就会对同一个互斥锁进行加锁。这时候就会发生死锁。但是你可以通过设置锁的属性来避免在这种情况下发生死锁。
pthread_mutexattr_init(&attr); 
// 设置 recursive 属性
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP); 
pthread_mutex_init(theMutex,&attr);


    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值