线程同步

一,线程同步概念

同步:同步即协同步调,按预定的先后次序运行。

线程同步:指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。

“同步”的目的:是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。

资源混乱的原因:

1. 资源共享(独享资源则不会)

2. 调度随机(意味着数据访问会出现竞争)

3. 线程间缺乏必要的同步机制。

线程同步的机制有互斥锁,读写锁,条件变量,信号量

二,互斥锁

1,互斥锁用于保护临界区,以保证任何时刻只有一个线程再执行其中的代码。

2,当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定。

因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。

互斥锁也可以用于进程间的同步

二值信号灯与互斥量的区别:

二值信号灯强调的是共享资源,只要共享资源可用,其他进程或线程同样可以修改信号灯的值;而互斥锁更强调进程或线程,占用资源的进程或线程使用完资源后,必须由进程或线程本身来解锁

 

3,相关函数

pthread_mutex_init函数

pthread_mutex_destroy函数

pthread_mutex_lock函数

pthread_mutex_trylock函数

pthread_mutex_unlock函数

以上5个函数的返回值都是:成功返回0, 失败返回错误号。

4,pthread_mutex_init函数(动态初始化)

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;(静态初始化)

功能:初始化一个互斥锁(互斥量) ---> 初值可看作1

mutex:全局定义的互斥变量 也叫互斥锁

attr:属性,一般为NULL

  • 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g.  pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
  • 动态初始化:局部变量应采用动态初始化。e.g.  pthread_mutex_init(&mutex, NULL)

2,pthread_mutex_destroy函数

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:销毁一个互斥锁

3,pthread_mutex_lock函数

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:加锁。可理解为将mutex--(或-1)

4,int pthread_mutex_unlock函数

#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:解锁。可理解为将mutex++(或+1)

mutex的值只有1和0。1表示锁可用,0表示锁不可用,被其他线程所占用

5,pthread_mutex_trylock函数

#include <pthread.h>

int pthread_mutex_trylock(pthread_mutex_t *mutex);

功能:尝试加锁

如果互斥锁被锁住会返回一个EBUSY错误

lock与unlock:

lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。

lock与trylock:

lock加锁失败会阻塞,等待锁释放。

trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。

在访问共享资源前加锁,访问结束后立即解锁(调用unlock)。锁的“粒度”应越小越好。

死锁:

1. 线程试图对同一个互斥量A加锁两次。

第二次加锁不会成功,就会一直阻塞。等到第一把锁解锁,但解锁是该线程解,然而他在阻塞。

2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁

对于第二种死锁。自己的锁没有解,去获取其他锁时,用pthread_mutex_trylock.不满足就会返回错误不会阻塞,然后作出让步,把自己的锁解了。

 

三,读写锁

1,与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。读锁、写锁并行阻塞,写锁优先级高

两种状态:1. 读模式下加锁状态 (读锁)

                  2. 写模式下加锁状态 (写锁)

三种情况:

1,读写锁是写模式加锁时,解锁前,所有对该锁要加锁的的线程进行阻塞(写独占)

2,读写锁是读模式加锁时,如果想要加锁的线程全是读模式加锁,则加锁成功(读共享)。如果想要加锁的线程全是写模式加锁,则这些线程阻塞

3,读写锁是读模式加锁时,如果要想加锁的线程中既有读模式加锁,又有写模式加锁,则阻塞。下次加锁时则写模式加锁的优先级高(写优先级高)。

2,假设情景描述:

一个线程现在加锁用于读,后来又来了两个用于读的线程则他们加锁成功。后来又来了一个读线程和写线程准备加锁,他们两个则阻塞。前面三个线程读完后,解锁后。写线程加锁,读线程先阻塞。在写线程加锁期间,如果又有其他线程要加锁则阻塞。

3,读写锁非常适合于对数据结构读的次数远大于写的情况。

4,主要应用函数:

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;

1,初始化一把读写锁:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

2,销毁一把读写锁:

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

3,以读方式请求读写锁。(常简称为:请求读锁)

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

4,以写方式请求读写锁。(常简称为:请求写锁)

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

5,解锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

6,非阻塞以读方式请求读写锁(非阻塞请求读锁)

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

7,非阻塞以写方式请求读写锁(非阻塞请求写锁)

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

注:函数具体用法与互斥锁差不多

例子:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int counter;
pthread_rwlock_t rwlock;

/* 3个线程不定时写同一全局资源,5个线程不定时读同一全局资源 */
void *th_write(void *arg)
{
    int t, i = (int)arg;
    while (1) {
        pthread_rwlock_wrlock(&rwlock);
        t = counter;
        usleep(1000);
        printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
        pthread_rwlock_unlock(&rwlock);
        usleep(10000);
    }
    return NULL;
}
void *th_read(void *arg)
{
    int i = (int)arg;

    while (1) {
        pthread_rwlock_rdlock(&rwlock);
        printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);
        pthread_rwlock_unlock(&rwlock);
        usleep(2000);
    }
    return NULL;
}
int main(void)
{
    int i;
    pthread_t tid[8];
    pthread_rwlock_init(&rwlock, NULL);

    for (i = 0; i < 3; i++)
        pthread_create(&tid[i], NULL, th_write, (void *)i);
    for (i = 0; i < 5; i++)
        pthread_create(&tid[i+3], NULL, th_read, (void *)i);
    for (i = 0; i < 8; i++)
        pthread_join(tid[i], NULL);

    pthread_rwlock_destroy(&rwlock);
    return 0;
}										

四,条件变量

1,为什么需要条件变量

互斥锁用于上锁,条件变量用于等待。互斥锁只有两种状态,锁定和非锁定。多个线程访问共享资源时,不知道何时去使用共享资源。

2,函数

pthread_cond_init函数

pthread_cond_destroy函数

pthread_cond_wait函数

pthread_cond_timedwait函数

pthread_cond_signal函数

pthread_cond_broadcast函数

以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。

pthread_cond_t类型 用于定义条件变量

pthread_cond_t cond;

1,初始化一个条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2,销毁一个条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

3,阻塞等待一个条件变量

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

三个作用:

1. 阻塞等待条件变量cond(参1)满足

2. 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);

 1.2.两步为一个原子操作。

3,当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

唤醒是指另外的线程调用pthread_cond_signal或pthread_cond_broadcast

4,限时等待一个条件变量

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

这里用的是绝对时间:

struct timespec {

time_t tv_sec; /* seconds */ 秒

long   tv_nsec; /* nanosecondes*/ 纳秒

}

time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。

struct timespec t = {1, 0};

pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去) 

正确用法:

time_t cur = time(NULL); 获取当前时间。

struct timespec t; 定义timespec 结构体变量t

t.tv_sec = cur+1; 定时1秒

pthread_cond_timedwait (&cond, &mutex, &t); 传参

5,唤醒至少一个阻塞在条件变量上的线程

int pthread_cond_signal(pthread_cond_t *cond);

6,唤醒全部阻塞在条件变量上的线程

int pthread_cond_broadcast(pthread_cond_t *cond);

 

互斥锁与条件变量的基本模型


对于生产者:

pthread_mutex_lock(&mutex);

    if(设置条件为真)

        pthread_cond_signal(&cond);

pthread_mutex_unlock(&mutex);

对于消费者:


pthread_mutex_lock(&mutex);

    while(条件为假)//while的作用是避免虚假唤醒

        pthread_cond_wait(&cond,&mutex);

pthread_mutex_unlock(&mutex);

避免上锁冲突

最坏的情况:当条件变量满足时,signal唤醒等待该条件变量的线程,该线程开始运行但立即停止,因为此线程没有获取互斥锁,是因为signal所在的线程还没有解锁

int dosignal;
pthread_mutex_lock(&mutex);
    dosignal=(条件满足);
    nready++;
pthread_mutex_unlock(&mutex);
if(dosignal)
    pthread_cond_signal(&cond,&cond);

例子:

//此条件变量是head不为空
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

struct msg {
    struct msg *next;
    int num;
};
struct msg *head;

pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p)
{
    struct msg *mp;
    for (;;) {
        pthread_mutex_lock(&lock);
        while (head == NULL) {           //头指针为空,说明没有节点    可以为if吗
            pthread_cond_wait(&has_product, &lock);
        }
        mp = head;      
        head = mp->next;    			//模拟消费掉一个产品
        pthread_mutex_unlock(&lock);

        printf("-Consume ---%d\n", mp->num);
        free(mp);
        sleep(rand() % 5);
    }
}
void *producer(void *p)
{
    struct msg *mp;
    while (1) {
        mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1;        //模拟生产一个产品
        printf("-Produce ---%d\n", mp->num);

        pthread_mutex_lock(&lock);
        mp->next = head;
        head = mp;
        pthread_mutex_unlock(&lock);

        pthread_cond_signal(&has_product);  //将等待在该条件变量上的一个线程唤醒
        sleep(rand() % 5);
    }
}
int main(int argc, char *argv[])
{
    pthread_t pid, cid;
    srand(time(NULL));

    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);

    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    return 0;
}							

互斥锁和条件变量的属性

pthread_mutexattr_t attr;

#include <pthread.h>

int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

int pthread_mutexattr_init(pthread_mutexattr_t *attr);

int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr,int *pshared);

int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);

 

pthread_condattr_t attr;

int pthread_condattr_destroy(pthread_condattr_t *attr);

int pthread_condattr_init(pthread_condattr_t *attr);

int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,

int *restrict pshared);

int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared);

get函数:pshared指向整数中这个属性的当前值

set函数:pshared设置这个属性的当前值。PTHREAD_PROCESS_PRIVATE或

PROCESS_SHARED后者是进程间共享的属性

以上函数都是成功返回0,失败返回错误号

 

五,信号量

1,信号量的分类

三种类型的信号量:

Posix有名信号量:使用Posix IPC名字标识,可用于进程或线程间的同步

Posix基于内存的信号量:存放在共享内存中,可用于进程或线程间的同步

System V信号量:在内核中维护,可用于进程或线程间同步。

Posix信号量不必再内核中维护。可能与文件系统中的路径名对应的名字来标识

互斥锁和信号量区别:

1,互斥锁必须总是由给它上锁的线程解锁,信号量的挂出却不必由执行过它的等待操作的同一线程执行。

2,互斥锁要么被锁住,要么被解开,

3,信号量有一个与之关联的状态,信号量挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么信号将丢失。

Posix信号量分为两种:

进化版的互斥量。互斥量只能取1,0.而信号量可取N。多个对象对同一资源共享,而互斥量只能一个对象对共享资源占用。

加锁:-- 解锁:++。值为0时,阻塞。大于0时才加锁成功

2,Posix基于内存的信号量函数(无名信号量)

主要函数:

sem_init函数

sem_destroy函数

sem_wait函数

sem_trywait函数

sem_timedwait函数

sem_post函数

sem_getvalue函数

以上7 个函数的返回值都是:成功返回0, 失败返回-1,同时设置errno。

sem_t类型:本质是结构体,但我们可以把她看成大于等于0的整数

1,sem_init函数

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

sem:sem_t定义的变量

pshared:为0时作用于线程,大于0(一般为1)作用于进程

value:信号量初值

2,sem_destroy函数

#include <semaphore.h>

int sem_destroy(sem_t *sem);

sem:sem_t定义的变量

功能:销毁一个信号量

3,sem_wait函数

#include <semaphore.h>

int sem_wait(sem_t *sem);

sem:sem_t定义的变量

功能:给信号量加锁 类似于pthread_mutex_lock

4,sem_trywait函数

#include <semaphore.h>

int sem_trywait(sem_t *sem);

sem:sem_t定义的变量

功能:尝试加锁,如果加锁不成功,不阻塞。类似于pthread_mutex_trylock

5,sem_timedwait函数

#include <semaphore.h>

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

sem:sem_t定义的变量

abs_timeout:绝对时间

功能:限时尝试对信号量加锁,类似于thread_cond_timelock

定时1秒:

time_t cur = time(NULL); 获取当前时间。

struct timespec t; 定义timespec 结构体变量t

t.tv_sec = cur+1; 定时1秒

sem_timedwait(&sem, &t); 传参

6,sem_post函数

#include <semaphore.h>

int sem_post(sem_t *sem);

sem:sem_t定义的变量

功能:解锁,类似于pthread_mutex_unlock.

7,sem_getvalue函数

#include <semaphore.h>

int sem_getvalue(sem_t *sem, int *sval);

sem:sem_t定义的变量

sval:传出参数,是一个整数取地址。表示信号量的当前值。

如果sval值0,表示已上锁,为某一个负数,其绝对值就是等待该信号解锁的线程数

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值