APUE学习之多线程编程(二):线程同步

为了保证临界资源的安全性和可靠性,线程不得不使用锁,同一时间只允许一个或几个线程访问变量。常用的锁有互斥量,读写锁,条件变量

一、互斥量

互斥量是用pthread_mutex_t数据类型表示的,在使用之前,必须对其进行初始化,可以把它设置为PTHREAD_MUTEX_INITIALIZER(只适于静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化,最后还要调用pthread_mutex_destroy进行释放。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

要用默认的属性初始化互斥量,只需把attr设为NULL,后面在讨论互斥量属性。

对互斥量进行加锁,使用pthread_mutex_lock,如果互斥量已经上锁,调用线程将阻塞至互斥量解锁,对互斥量解锁,使用pthread_mutex_unlock,如果线程不希望被阻塞,它可以调用pthread_mutex_trylock尝试对互斥量进行加锁,如果互斥量未锁住,则成功加锁,如果互斥量已锁住,pthread_mutex_trylock就会失败,返回EBUSY。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

例子:

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

struct foo
{
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
};

struct foo * foo_alloc(int id)
{
    struct foo *fp = NULL;

    if ((fp = malloc(sizeof(struct foo))) != NULL)
    {
        fp->f_count = 1;
        fp->f_id = id;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0)
        {
            free(fp);
            return NULL;
        }
    }

    return fp;
}

void foo_hold(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}

void foo_rele(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);

    if (--fp->f_count == 0)
    {
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    }
    else
    {
        pthread_mutex_unlock(&fp->f_lock);
    }
}

上面的例子描述了用于保护某个数据结构的互斥量,我们在对象中嵌入引用计数,确保在所有使用该对象的线程完成数据访问之前,该对象的内存空间不会被释放。

如果线程对同一个互斥量加锁两次,那么它自身将陷入死锁状态。如果有一个以上的互斥量,且允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量,也阻塞,就死锁了。

可以通过仔细控制互斥量加锁的顺序来避免死锁的发生,譬如要求所有线程必须先锁住互斥量A才能锁住互斥量B。另一种办法是当线程无法获得下一个互斥量的时候,就释放自己已占有的互斥量,过一段时间再试。

例子:

#include "apue.h"
#include <pthread.h>

#define NMASH 29
#define HASH(id) (((unsigned long)id) % NMASH)

struct foo *fh[NMASH];

pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;

struct foo
{
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
    struct foo *f_next;
};

struct foo *foo_alloc(int id)
{
    struct foo *fp = NULL;
    int idx = 0;

    if ((fp = malloc(sizeof(struct foo))) != NULL)
    {
        fp->f_count = 1;
        fp->f_id = if;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0)
        {
            free(fp);
            return NULL;
        }

        idx = HASH(id);
        pthread_mutex_lock(&hashlock);
        fp->f_next = fh[idx];
        fh[idx] = fp;
        pthread_mutex_lock(&fp->f_lock);
        pthread_mutex_unlock(&hashlock);
        pthread_mutex_unlock(&fp->f_lock);
    }

    return fp;
}

void foo_hold(struct foo *fp)
{
    pthread_mutex_lock(&fp->f_lock);
    fp->f_count++;
    pthread_mutex_unlock(&fp->f_lock);
}

struct foo *foo_find(int id)
{
    struct foo *fp = NULL;

    pthread_mutex_lock(&hashlock);

    for (fp = fh[HASH(id)]; fp != NULL; fp = fp->next)
    {
        if (fp->f_id = id)
        {
            foo_hold(fp);
            break;
        }
    }

    pthread_mutex_unlock(&hashlock);
    return fp;
}

void foo_rele(struct foo *fp)
{
    struct foo *tfp = NULL;
    int idx = 0;

    pthread_mutex_lock(&fp->f_lock);

    if (fp->f_count == 1)
    {
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_lock(&hashlock);
        pthread_mutex_lock(&fp->f_lock);

        if (fp->f_count != 1)
        {
            fp->f_count--;
            pthread_mutex_unlock(&hashlock);
            pthread_mutex_unlock(&fp->f_lock);
            return;
        }

        idx = HASH(fp->f_id);
        tfp = fh[idx];
        if (tfp = fp)
        {
            fh[idx] = fp->f_next
        }
        else
        {
            while(tfp->next != fp)
            {
                tfp = tfp->next;
            }
            tfp->next = fp->f_next;
        }

        pthread_mutex_unlock(&hashlock);
        pthread_mutex_unlock(&fp->f_lock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    }
    else
    {
        fp->f_count--;
        pthread_mutex_unlock(&fp->f_lock);
    }
}

这个例子比上一个例子多了一个散列表和一个保护散列表的互斥量,加锁的顺序是先hashlock,再f_lock,注意这个顺序,就不会发生死锁,不过这样也导致代码太繁琐,最后一个函数解锁f_lock后重新加锁f_lock,需要重新考察f_count的值,因为可能在这期间被其他线程修改。

这样的方式太复杂,让hashlock也保护f_cout,事情会简单很多。

例子:

#include "apue.h"
#include <pthread.h>

#define NMASH 29
#define HASH(id) (((unsigned long)id) % NMASH)

struct foo *fh[NMASH];

pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;

struct foo
{
    int f_count;
    pthread_mutex_t f_lock;
    int f_id;
    struct foo *f_next;
};

struct foo *foo_alloc(int id)
{
    struct foo *fp = NULL;
    int idx = 0;

    if ((fp = malloc(sizeof(struct foo))) != NULL)
    {
        fp->f_count = 1;
        fp->f_id = if;
        if (pthread_mutex_init(&fp->f_lock, NULL) != 0)
        {
            free(fp);
            return NULL;
        }

        idx = HASH(id);
        pthread_mutex_lock(&hashlock);
        fp->f_next = fh[idx];
        fh[idx] = fp;
        pthread_mutex_lock(&fp->f_lock);
        pthread_mutex_unlock(&hashlock);
        pthread_mutex_unlock(&fp->f_lock);
    }

    return fp;
}

void foo_hold(struct foo *fp)
{
    pthread_mutex_lock(&hashlock);
    fp->f_count++;
    pthread_mutex_unlock(&hashlock);
}

struct foo *foo_find(int id)
{
    struct foo *fp = NULL;

    pthread_mutex_lock(&hashlock);

    for (fp = fh[HASH(id)]; fp != NULL; fp = fp->next)
    {
        if (fp->f_id = id)
        {
            foo_hold(fp);
            break;
        }
    }

    pthread_mutex_unlock(&hashlock);
    return fp;
}

void foo_rele(struct foo *fp)
{
    struct foo *tfp = NULL;
    int idx = 0;

    pthread_mutex_lock(&hashlock);

    if (fp->f_count == 1)
    {

        idx = HASH(fp->f_id);
        tfp = fh[idx];
        if (tfp = fp)
        {
            fh[idx] = fp->f_next
        }
        else
        {
            while(tfp->next != fp)
            {
                tfp = tfp->next;
            }
            tfp->next = fp->f_next;
        }

        pthread_mutex_unlock(&hashlock);
        pthread_mutex_destroy(&fp->f_lock);
        free(fp);
    }
    else
    {
        fp->f_count--;
        pthread_mutex_unlock(&hashlock);
    }
}

当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock和pthread_mutex_lock是基本等价的,但是达到超时时间后,pthread_mutex_timedlock会返回。超时时间指原意等待的绝对时间。这个超时时间是用timespec来表示的

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);

二、读写锁

读写锁与互斥量相似,不过读写锁允许更高的并行性,一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁,简单地来说,就说支持一个写者,多个读者。

当读写锁是写加锁状态时,所以试图对这个锁加锁的线程都会被阻塞,当读写锁在读加锁状态时,所以试图以读模式对它进行加锁的线程都可以得到访问权,但是希望以写模式加锁的线程会被阻塞。不过当有一个线程企图以写模式获取锁时,读写锁会阻塞后面的读模式锁请求,防止读模式锁长期占用。

可知,读写锁适用于对数据结构读的次数远大于写的情况,又称共享互斥锁,读共享,写互斥。

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

读写锁调用phtread_rwlock_init进行初始化,如果希望读写锁有默认的属性,传null给attr即可。

读的模式下锁定读写锁,需要调用phtread_rwlock_rdlock,写的模式下锁定读写锁,需要调用pthread_rwlock_wrlock,不过以何种方式锁定读写锁,都可以调用pthread_rwlock_unlock解锁。

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

例子:

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

struct job
{
    struct job *j_next;
    struct job *j_prev;
    pthread_t j_id;
};

struct queue
{
    struct job *q_head;
    struct job *q_tail;
    pthread_rwlock_t q_lock;
};

int queue_init(struct queue *qp)
{
    int err;

    qp->q_head = NULL;
    qp->q_tail = NULL;
    err = pthread_rwlock_init(&qb->q_lock, NULL);
    if (err != 0)
    {
        return err;
    }

    return 0
}

void job_insert(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qb->q_lock);
    jp->next = qp->head;
    jp->j_prev = NULL;

    if (qp->q_head != NULL)
    {
        qp->q_head->j_prev = jp;
    }
    else
    {
        qp->tail = jp;
    }
    qp->head = jp;
    pthread_rwlock_unlock(&qp->q_lock);
}

void job_append(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);
    jp->j_next = NULL;
    jp->j_prev = qp->tail;
    if (qp->q_tail != NULL)
    {
        qp->q_tail->j_next = jp;
    }
    qp->q_tail = jp;
    pthread_rwlock_unlock(&qp->q_lock);
}

void job_remove(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);
    if (jp == qp->q_head)
    {
        qp->q_head = jp->j_next;
        if (qp->q_tail == jp)
        {
            qp->tail = NULL;
        }
        else
        {
            jp->next->j_prev = jp->j_prev;
        }
    }
    else if (jp == qp->q_tail)
    {
        qp->q_tail = jp->j_prev;
        jp->j_prev->j_next = NULL;
    }
    else
    {
        jp->j_prev->j_next = jp->j_next;
        jp->j_next->j_prev = jp->j_prev;
    }
    pthread_rwlock_unlock(&qp->q_lock);
}

struct job *job_find(struct queue *qp, pthread_t id)
{
    struct job *jp;

    if (pthread_rwlock_rdlock(&qp->q_lock) != 0)
    {
        return NULL;
    }

    for (jp = qb->q_head; jp != NULL; jp = jp->j_next)
    {
        if (pthread_equal(jp->j_id, id))
        {
            break;
        }
    }
    pthread_rwlock_unlock(&qp->q_lock);
    return jp;
}

与互斥量一样,读写锁也有带超时的读写锁函数,避免陷入永久的阻塞。

#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);

三、条件变量

条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

条件本身由互斥量保护,线程在改变条件状态之前必须锁定互斥量。在使用条件变量之前,必须把它初始化,可以把常量PTHREAD_CON_INITIALIZE赋给静态分配的条件变量,也可用pthread_cond_init函数进行初始化。使用pthread_cond_destroy释放。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_con_t *cond);

如果需要一个默认属性的条件变量,把null给attr即可。

我们使用pthread_cond_wait等待条件变量为真,如果在给定时间内不能满足,则返回错误码。

#include<pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)
int pthread_cond_timedwait(pthread_cond_t *restrict cond, phtread_mutex_t *restrict mutex, const struct timespec *restrict tsptr)

调用者把锁定的互斥量传给函数,函数自动把调用线程放到等待条件的线程列表上,对互斥量解锁,当pthread_cond_wait返回时,互斥量再次被锁住。pthread_cond_timedwait多了原意等待的时间。

有两个函数可用于通知线程条件已满足,pthread_cond_signal函数至少唤醒一个,pthread_cond_broadcast唤醒等待该条件的所有线程。

#include<phtread.h>
int pthread_cond_signal(pthread_cond_t *cond)
int pthread_cond_broadcast(pthread_cond_t *cond)

例子:

#include <pthread.h>

struct msg
{
    struct msg *m_next;
};

struct msg *workq;

pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

void process_msg(void)
{
    struct msg *mp;

    for(;;)
    {
        pthread_mutex_lock(&qlock);
        while (workq == NULL)
        {
            pthread_cond_wait(&qready, &qlock);
        }

        mp = workq;
        workq = mp->m_next;
        pthread_mutex_unlock(&qlock);
    }
}

void enqueue_msg(struct msg *mp)
{
    pthread_mutex_lock(&qlock);
    mp->m_next = workq;
    workq = mp;
    pthread_mutex_unlock(&qlock);
    pthread_cond_signal(&qready);
}

时间: 2024-09-30 04:53:13

APUE学习之多线程编程(二):线程同步的相关文章

Linux程序设计学习笔记----多线程编程之线程同步之条件变量

转载请注明出处:http://blog.csdn.net/suool/article/details/38582521. 基本概念与原理 互斥锁能够解决资源的互斥访问,但是在某些情况下,互斥并不能解决问题,比如两个线程需 要互斥的处理各自的操作,但是一个线程的操作仅仅存在一种条件成立的情况下执行,一旦错过不可再重现,由于线程间相互争夺cpu资源,因此在条件成立的时候,该线程不一定争夺到cpu而错过,导致永远得不到执行..... 因此需要某个机制来解决此问题,更重要的是,线程仅仅只有一种情况需要执

.NET面试题解析(07)-多线程编程与线程同步

系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等,本文只是从常见面试题的角度(也是开发过程中常用)去深入浅出线程相关的知识.如果想要系统的学习多线程,没有捷径的,也不要偷懒,还是去看专业书籍的比较好. 常见面试题目: 1. 描述线程与进程的区别? 2. 为什么GUI不支持跨线程访问控件?一般如何解决这个问题? 3. 简述后台线程和前台线程的区别? 4. 说说常

C# 多线程编程第二步——线程同步与线程安全

上一篇博客学习了如何简单的使用多线程.其实普通的多线程确实很简单,但是一个安全的高效的多线程却不那么简单.所以很多时候不正确的使用多线程反倒会影响程序的性能. 下面先看一个例子 : class Program { static int num = 1; static void Main(string[] args) { Stopwatch stopWatch = new Stopwatch(); //开始计时 stopWatch.Start(); ThreadStart threadStart

Delphi中线程类TThread实现多线程编程(线程同步技术、Synchronize、WaitFor……)

接着上文介绍TThread. 现在开始说明 Synchronize和WaitFor 但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区 事件(Event)与Delphi中的事件有所不同.从本质上讲,Event其实就相当于一个全局的布尔变量.它有两个赋值操作:Set和ReSet,相当于把它设置为 True或False.而检查它的值是通过WaitFor操作进行.对应在Windows平台上,是三个API函数:SetEvent.ResetEvent.WaitForSignalObje

iOS多线程编程:线程同步总结 NSCondtion

1:原子操作 - OSAtomic系列函数 iOS平台下的原子操作函数都以OSAtomic开头,使用时需要包含头文件<libkern/OSBase.h>.不同线程如果通过原子操作函数对同一变量进行操作,可以保证一个线程的操作不会影响到其他线程内对此变量的操作,因为这些操作都是原子式的.因为原子操作只能对内置类型进行操作,所以原子操作能够同步的线程只能位于同一个进程的地址空间内. 2:锁 - NSLock系列对象 iOS平台下的锁对象为NSLock对象,进入锁通过调用lock函数,解锁调用unl

APUE学习之多线程编程(三):线程属性、同步属性

一.线程属性 可以使用pthread_attr_t结构修改线程默认属性,并这些属性和创建的线程练习起来,可以使用pthread_att_init函数初始化pthread_attr_t结构,调用pthread_attr_init后,pthread_attr_t结构所包含的就是操作系统实现支持的所有线程属性的默认值. pthread_attr_destroy用于销毁属性对象,释放资源. #include <pthread.h> int pthread_attr_init(pthread_attr_

七. 多线程编程8.线程同步

当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用.达到此目的的过程叫做同步(synchronization).像你所看到的,Java为此提供了独特的,语言水平上的支持. 同步的关键是管程(也叫信号量semaphore)的概念.管程是一个互斥独占锁定的对象,或称互斥体(mutex).在给定的时间,仅有一个线程可以获得管程.当一个线程需要锁定,它必须进入管程.所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程.这些其他的线程被称为等待管程.一个

廖雪峰Java11多线程编程-2线程同步-2synchronized方法

1.Java使用synchronized对一个方法进行加锁 class Counter{ int count = 0; public synchronized void add(int n){ count += n; } public synchronized void dec(int n){ count -= n; } public int get(){//读取一个int类型是原子操作,不需要同步 return count; } } class AddThread extends Thread

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量