UNIX网络编程:互斥锁和条件变量
Posted Dandelion_gong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UNIX网络编程:互斥锁和条件变量相关的知识,希望对你有一定的参考价值。
在网络编程中,一般都是多线程的编程,这就出现了一个问题:数据的同步与共享。而互斥锁和条件变量就是为了允许在线程或进程间共享数据、同步的两种最基本的组成部分。它们总能够用来同步一个进程中的多个线程。
再进入互斥锁和条件变量之前,我们先对多线程的一些相关函数进行简单介绍:
多线程简单介绍和相关函数:
通常,一个进程中包括多个线程,每个线程都是CPU进行调度的基本单位,多线程可以说是在共享内存空间中并发地多道执行程序,与进程相比,线程的具有以下优点:
● 减少系统调度开销。由于线程基本不拥有资源,因此线程的切换非常迅速,提高了运行速度,是程序的执行效率更高。
● 线程间通讯方便。由于统一进程的多个线程共享资源,一个线程的数据可以直接为其他线程所用,这使得它们之间的数据交换更加方便快捷。
● 改善程序设计结构。功能复杂的进程可以分为多个独立的线程分别执行,程序结构清晰,模块性更强。
线程技术早在20世纪60年代就被提出了,但直到20世纪80年代中期,才把多线程真正应用到操作系统中去。
线程有两种实现方法:用户态线程和核心态线程,目前线程主要的实现方法是用户态线程,用户态的多线程程序在运行时不需要特定的内核支持,同一个进程的线程之间进行调度切换时,不需要调用系统调用,这使得线程操作的系统开销减小,但在一个进程中的多个线程的调度中无法发挥多处理的优势,核心态线程的实现方法允许不同进程中的线程按照相同的调度方法进行调度,有利于发挥多处理器的并发优势。
2、 Linux多线程操作
编写Linux下的多线程程序,需要使用头文件pthread.h,这个头文件里定义了线程操作相关的API以及数据结构,编译链接时需要加上编译器的 –lpthread 选项,指明需要的线程库文件。
1、线程创建函数
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数1(thread):创建线程所用的线程tid号。
参数2(attr): 用来设置新线程的属性,如果使用默认属性,可以填写NULL,还可以使用系统提供的函数,将线程的属性设置为我们所需要的属性。线程的属性主要包括分离属性,绑定属性,调度属性,堆栈属性,继承属性等,属性结构为pthread_attr_t,在usr/include/pthread.h中定义,属性值不能直接设置,需要使用相关函数进行操作,初始化的函数为pthread_attr_init()。必须在线程创建函数pthread_create()之前进行。
参数3:该线程锁钥执行的操作的入口地址。
参数4:该线程所执行函数的参数,没有的花为NULL。
2、等待线程结束函数:
int pthread_join(pthread_t thread, void **retval);
用来等待某个指定线程的结束。
参数1:要等待的线程的tid号;
参数2:所等线程存在状态的返回值,一半设为NULL。
3、线程终止函数
void pthread_exit(void *retval);
在非 隐式调用主线程的结果,任何线程的启动函数返回pthread_exit(),使用函数的返回值为线程的退出状态。
允许其他线程继续执行,主线程通过调用应该终止pthread_exit()
互斥锁:
互斥锁只待相互排斥,它是最基本的同步形式。互斥锁用于保护临界区(同个进程不同线程均可访问的区域),以保证任何时刻只有一个线程在执行其中的代码或者任何时刻只有一个进程在执行其中的代码。
互斥量从本质上说相当于一把锁,提供对共享资源的保护访问,互斥量有两种状态,lock和unlock,用来保证一段时间内只有一个线程使用共享资源。
保护一个临界区大值操作如下:
pthread_mutex_lock(...); //对某个线程上锁
临界区;
pthread_mutex_unlock(...); //对相应的线程解锁
在Linux下,线程的互斥量数据类型是pthread_mutex_t,互斥量操作涉及的函数主要有:
1、初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
参数1:一个metux类型的互斥锁的地址。
参数2:指定了新建互斥锁的属性。如果参数attr为NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
pthread_mutexattr_init()函数成功完成之后会返回零,其他任何返回值都表示出现了错误。函数成功执行后,互斥锁被初始化为锁住态。
2、线程上锁函数
int pthread_mutex_lock(pthread_mutex_t *mutex);
3、线程尝试上锁函数
int pthread_mutex_trylock(pthread_mutex_t *mutex);
4、线程解锁函数
int pthread_mutex_unlock(pthread_mutex_t *mutex);
5、线程销毁互斥锁函数
int pthread_mutex_destroy(pthread_mutex_t *mutex);
条件变量:
2、 条件变量
只用互斥量很可能会引起死锁,为此引入了条件变量,条件变量允许线程阻塞和等待另一个线程发送的信号,使用条件变量可以以原子方式阻塞线程,直到满足某个条件为止,可以避免忙等。
条件变量常和互斥锁一起使用,互斥量主要用来保证对临界区的互斥进入,而条件变量则用于线程的阻塞等待,互斥锁定进入临界区以后,若条件不满足,线程便转为等待状态,等待条件满足后被唤醒执行,否则继续执行,执行完后开锁。
条件变量类型为pthread_cond_t,相关操作函数为:
1、函数初始化:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
按参数 attr 指定的属性创建一个新的条件变量。
参数1:所要创建的条件变量。
参数2 :条件变量的属性。attr是NULL,则使用默认的属性创建条件变 量。这是对动态分配的互斥锁的初始化办法,对于静态分配的互斥锁可以直接赋值初始化:pthread_cond_tcond=PTHREAD_COND_INITIALIZER;
任何一个条件变量在被使用前,都必须初始化,且只能初始化一次(如果是被销毁了的条件变量,就可以重新初始化)。
2、通知条件变量函数
(1) int pthread_cond_signal(pthread_cond_t *cond);
该函数用来解除一个等待指定事件的线程的阻塞状态,如果有若干线程挂起等待该条件变量,该调用只唤起一个线程,被唤起的线程并不确定。
参数cond:指定事件的条件量。
(2) int pthread_cond_broadcast(pthread_cond_t *cond);
该函数与用来对所有等待这个条件变量的线程解除阻塞。
参数cond:指定事件的条件变量。
3、等待条件变量函数
(1)int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
该函数的作用是等待一个事件(条件变量)通知,例如上面的例子,在测试a是否为0后,如果不是为0,那么就调用该函数,将线程挂起,等待对应条件变量事件的通知(被阻塞线程直到有其他线程调用pthread_cond_signal或pthread_cond_broadcast函数置相应的条件变量时才被唤醒)。这些因为等待某个特定条件变量的线程组成一个队列,这个队列与这个特定的条件变量直接相关。
参数1(cond):所等待的条件变量。
参数2(mutex):所使用的互斥锁。
(2)int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
该函数比上一个函数多了一个时间参数,其作用是在一段指定时间内等待一个事件的发生,如果在指定的时间内没有被唤醒,那么函数就返回ETIMEDOUT(110)值,表示超时。这个非常有用,可以用来指定线程睡眠时间,值得注意的是,这个时间参数是绝对时间,而不是时间间隔。
参数1(cond):所等待的条件变量。
参数2(mutex):所使用的互斥锁。
参数3(abstime):指定等待的时间范围。
一般情况下通知条件变量函数(pyhread_cond_signal())与等待条件变量函数(pthread_cond_wait())配合使用,来达到对共享资源的保护作用。
下面是一个简单的程序代码来展现互斥锁与条件变量的“魔法”:
创建连个线程分别来处理value为奇数和偶数的情况 ,用互斥锁与条件变量来对共享数据value进行管理。
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <stdio.h>
#define MAX_SIZE 10
int value = 0;
pthread_mutex_t mutex; //定义一个全局的互斥锁
pthread_cond_t dan, shuang; //定义两个条件变量来表示单数信号和双数信号
void * pthread_fun1(void *arg)
{
pthread_mutex_lock(&mutex); //对共享数据value上锁
while(value < MAX_SIZE){
if(value % 2 == 1){ //value为奇数
printf("fun1 value = %d\n", value);
value++; //value值加1
pthread_cond_signal(&shuang); //发送为双数信号。通知双数的处理线程pthread_fun2().
}else{ //value不为奇数
pthread_cond_wait(&dan, &mutex); //阻塞程序,并解开互斥锁。等待接收到条件变量dan后再进行上锁并使函数继续运行。
}
}
pthread_mutex_unlock(&mutex); //对线程解锁。
}
void* pthread_fun2(void *arg)
{
pthread_mutex_lock(&mutex); //对该线程上锁,如果该互斥量已经被上锁则程序阻塞在该处,直到其他线程对其解锁,它的到资源为止。
while(value < MAX_SIZE){ //如果vallue在预定范围内
if(value % 2 == 0){ //value是偶数
printf("fun2 value = %d\n", value);
value ++;
pthread_cond_signal(&dan); //发送为双数信号。通知奇数的处理线程pthread_fun1().
}else{
pthread_cond_wait(&shuang, &mutex); //阻塞程序,并解开互斥锁。等待接收到条件变量 shuang 后再进行上锁并使函数继续运行
}
}
pthread_mutex_unlock(&mutex); //该线程运行结束前,解开互斥锁。
}
int main()
{
pthread_mutex_init(&mutex,NULL); //互斥锁初始化
pthread_t tid[2];
pthread_create(&tid[1], NULL, pthread_fun2, &tid[0]); //创建一个线程用来执行pthread_fun2()操作。
sleep(1);
pthread_create(&tid[0], NULL, pthread_fun1, NULL); //创建一个线程,用来执行pthread_fun1()操作。
pthread_join(tid[0], NULL); //等待线程1结束
pthread_join(tid[1], NULL); //等待线程2结束
pthread_mutex_destroy(&mutex); //销毁互斥锁。
return 0;
}
以上是关于UNIX网络编程:互斥锁和条件变量的主要内容,如果未能解决你的问题,请参考以下文章