UNIX多线程编程(2) 线程互斥

本文接着上篇《UNIX多线程编程(1)》讲解多线程的同步与互斥问题。
首先来看下关于线程同步互斥的概念性的知识,相信大家通过前面的文章,已经对线程同步互斥有一定的认识了,也能模糊的说出线程同步互斥的各种概念性知识,下面再列出从《计算机操作系统》一书中选取的一些关于线程同步互斥的描述。相信先有个初步而模糊的印象再看下权威的定义,应该会记忆的特别深刻。

1.线程(进程)同步的主要任务

答:在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理出错,因此线程同步的主要任务是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。

2.线程(进程)之间的制约关系?

当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。

(1).间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。

(2).直接相互制约。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。

间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。

程序描述:

主线程启动10个子线程并将表示子线程序号的变量地址作为参数传递给子线程。子线程接收参数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出参数和全局变量。
要求:
1.子线程输出的线程序号不能重复。
2.全局变量的输出必须递增。
下面画了个简单的示意图
这里写图片描述
分析下这个问题的考察点,主要考察点有二个:

1.主线程创建子线程并传入一个指向变量地址的指针作参数,由于线程启动须要花费一定的时间,所以在子线程根据这个指针访问并保存数据前,主线程应等待子线程保存完毕后才能改动该参数并启动下一个线程。这涉及到主线程与子线程之间的同步。

2.子线程之间会互斥的改动和输出全局变量。要求全局变量的输出必须递增。这涉及到各子线程间的互斥。
信号量和互斥锁实现版本如下:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include <unistd.h>
#include <semaphore.h>
pthread_mutex_t mutex;
sem_t sem;
int g_nCount;
void* mythread(void*pPM)
{
    int nThreadNum = (long)pPM;

    pthread_mutex_lock(&mutex);
    g_nCount++;
    pthread_mutex_unlock(&mutex);

   printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nCount);
    return 0;
}

void* mythread1(void*pPM)
{
    int nThreadNum = (long)pPM;

    sem_wait (&sem);
    g_nCount++;
    sem_post (&sem);

   printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nCount);
    return 0;
}


int main()
{
    printf(" 子线程报数\n");
    const int THREAD_NUM = 2000;
    pthread_t id[THREAD_NUM] ;
    pthread_mutex_init(&mutex, NULL);

    sem_init (&sem, 0, 1);

    for (int i = 0; i < THREAD_NUM; i++)
        pthread_create(&id[i], NULL, mythread, (void*)i);
    for (int i = 0; i < THREAD_NUM; i++)
        pthread_join(id[i], NULL);
    printf("有%d个用户登录后记录结果是%d\n", THREAD_NUM, g_nCount);

    for (int i = 0; i < THREAD_NUM; i++)
        pthread_create(&id[i], NULL, mythread1, (void*)i);
    for (int i = 0; i < THREAD_NUM; i++)
        pthread_join(id[i], NULL);
    sem_destroy(&sem);
    pthread_mutex_destroy(&mutex);
    return 0;
}

输出结果:线程编号为1998 全局资源值为3993
线程编号为1999 全局资源值为3994
线程编号为1670 全局资源值为3995
线程编号为1668 全局资源值为3996
线程编号为1666 全局资源值为3997
线程编号为1662 全局资源值为3999
有2000个用户登录后记录结果是4000

互斥锁
互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,即上锁和解锁,可以把互斥锁看做某种意义上的全局变量。在同一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会被挂起,直到上锁的线程释放掉互斥锁为止。可以说,这把互斥锁保证让每个线程对共享资源按顺序进行原子操作。
互斥锁机制主要包括以下基本函数:
● 互斥锁初始化:pthread_mutex_init()
● 互斥锁上锁:pthread_mutex_lock()
● 互斥锁判断上锁:pthread_mutex_trylock()
● 互斥锁解锁:pthread_mutex_unlock()
● 消除互斥锁:pthread_mutex_destroy()
其中,互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。这3种锁的区别主要在于其它未占有互斥锁的线程在希望得到互斥锁时是否需要阻塞等待。快速互斥锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止;递归互斥锁能够成功地返回,并且增加调用线程在互斥上加锁的次数;而检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。默认属性为快速互斥锁。

所需头文件include<pthread.h>
函数原型int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
函数参数mutex: 互斥锁标识符 mutexattr:PTHREAD_MUTEX_TIMED_NP:互斥锁PTHREAD_MUTEX_RECURSIVE_NP:递归锁 PTHREAD_MUTEX_ERRORCHECK_NP:检错锁
函数返回值成功:0 出错:返回错误码

















所需头文件#include<pthread.h>
函数原型#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)
函数返回值 成功:0 出错:返回-1


mutex的锁结构:

struct mutex {
  48        /* 1: unlocked, 0: locked, negative: locked, possible waiters */
  49        atomic_t                count;
  50        spinlock_t              wait_lock;
  51        struct list_head        wait_list;
  52#ifdef CONFIG_DEBUG_MUTEXES
  53        struct thread_info      *owner;
  54        const char              *name;
  55        void                    *magic;
  56#endif
  57#ifdef CONFIG_DEBUG_LOCK_ALLOC
  58        struct lockdep_map      dep_map;
  59#endif
  60};

各字段详解:

1、atomic_t count; 指示互斥锁的状态:
1 没有上锁,可以获得
0 被锁定,不能获得
负数 被锁定,且可能在该锁上有等待进程
初始化为没有上锁。

2、spinlock_t wait_lock;等待获取互斥锁中使用的自旋锁。
在获取互斥锁的过程中,操作会在自旋锁的保护中进行。初始化为为锁定。

3、struct list_head wait_list;等待互斥锁的进程队列。

信号量
信号量就是操作系统中多用到的PV原子操作,它广泛应用于进程或线程间的同步与互斥。这里写图片描述
Linux实现了Posix的无名信号量,主要用于线程间的互斥与同步。这里主要介绍几个常见函数:
● sem_init()用于创建一个信号量,并初始化它的值。
● sem_wait()和sem_trywait()都相当于P操作,在信号量>0时,它们能将信号量的值减1。两者的区别在于信号量<0时,sem_wait(0将会阻塞进程,而sem_trywait则会立即返回。
● sem_post()相当于V操作,它将信号量的值加1,同时发出信号来唤醒等待的进程。
● sem_getvalue()用于得到信号量的值。
● sem_destroy()用于删除信号量。
semphore结构

struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
}

信号量在Linux中的实现是没任何问题的(上来先安抚一下大家躁动的心情),但是mutex的语义相对来说要较信号量要来得 简单,所以如果你的代码若只是想对某一共享资源进行互斥访问的话,那么使用这种简化了的mutex机制可以带来如下的一坨好处。
semaphore和mutex的代码实现中都有fast path和slow path两条路径,所谓的fast path,就是当前的代码直接获得信号量,而所谓的slow path,则是当前代码没能第一时间获得信号量。semaphore和mutex在fast path上性能上的变化应该微乎其微,这个在metex-design.txt文档中也有说明。两者之间最大的差别来自对slow path的处理.
两者之前的差别请看参考文献[3].《深层次探讨mutex与semaphore之间的区别》
参考文献:
[1].http://blog.csdn.net/lihenair/article/details/6597080
[2].http://blog.chinaunix.net/uid-29235952-id-4223902.html
[3].http://blog.chinaunix.net/uid-23769728-id-3167583.html
下一篇讲解线程同步的问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值