所谓有名就是给一个类似句柄的key。
1.创建并初始化有名信号灯
函数:sem_open
#include<semaphore.h>
sem_t *sem_open( const char *name, int oflag, mode_t mode, unsigned intval );
参数:
name:信号灯的名称
oflag:选择创建或者打开一个现有的信号灯
mode:权限位(进程中已经讲过,差不多)
val:信号灯的初始值
成功时返回指向信号灯的指针,出错时为SEM_FAILED
oflag可以取值为:0,O_CREAT(创建),O_CREAT | O_EXCL(没有指定就创建)
if
oflag指定了参数有O_CREAT,那么后面的mode和val参数是必须的!!!
注意:val的值大小不能超过SEM_VALUE_MAX < 32767。
一般有两种信号灯:
互斥信号灯(二值),所以val = 1
计数信号灯,一般val > 1
注意参数
O_CREAT与O_CREAT | O_EXCL的区别
>>>>>>>:
前者的意思是:
如果所需信号灯尚未存在,那就创建并初始化它
所以即使是已经存在此信号量,指定此参数也不会报错。
后者则不一样,其实是保证了唯一性,也就是if已经有了,那么就会报错。
2.sem_close:关闭有名信号灯
#include<semaphore.h>
intsem_close( sem_t * sem);
//!> 注意是以key关闭
若成功则返回0,否则返回-1。
>>>>>:
一个进程终止时,内核还对其上仍然打开着的所有有名信号灯自动执行这样的信号灯关闭操作。
但应注意的是关闭一个信号灯并没有将它从系统中删除。
有名信号灯是随内核持续的,即使当前没有进程打开着某个信号灯,它的值仍然保持。
3.sem_unlink:从系统中删除信号灯
#include<semapthore.h>
intsem_unlink( const char * name);
//!> 注意是以name删除的!
若成功则返回0,否则返回-1。
//!> 注意与sem_close的区别
4. sem_getvalue:测试信号灯
#include<semaphore.h>
intsem_getvalue( sem_t * sem, int * val );
若成功则返回0,否则返回-1。
>>>>>:
sem_getvalue在由val指向的正数中返回所指定信号灯的当前值。如果该信号灯当前已上锁,
那么返回值或为0,或为某个负数,其绝对值就是等待该信号灯解锁的线程数。
5. 等待共享src(互斥使用):sem_wait和 sem_trywait
#include<semaphore.h>
intsem_wait( sem_t * sem );
intsem_trywait( sem_t * sem );
若成功则返回0,否则返回-1。
具体执行:
我们可以用sem_wait来申请共享资源,sem_wait函数可以测试所指定信号
灯的值,如果该值大于0,那就将它减1并立即返回。我们就可以使用申请来的共享
资源了。如果该值等于0,调用线程就被进入睡眠状态,直到该值变为大于0,这时
再将它减1,函数随后返回。sem_wait操作必须是原子操作。
( 注意与SYSTEMV 中的P, V 操作是一样的... ... )
对于sem_trywait一般的功能与sem_wait差不多,主要差别是:当所指定信号灯的值已
经是0时,后者并不将调用线程投入睡眠。相反,它返回一个EAGAIN错误。
6. 解锁操作( 就是V操作),也就是有sem_wait相反的操作!!!
sem_post
#include<semaphore.h>
intsem_post( sem_t * sem );
若成功则返回0,否则返回-1。
注意:信号灯创建OK后,就是已经存在于system中了,即使是不同的程序中使用都是可以的,只要没有被删除而且没有被其他程序占用!!!
二. 基于内存的信号灯
由应用程序分配信号灯的内存空间,然后由系统初始化它们的值。
1.初始化和关闭信号灯:sem_init和sem_destroy
#include<semaphore.h>
intsem_init( sem_t * sem, int shared, unsigned int value );
intsem_getvalue( sem_t * sem );
参数:
sem
指向信号灯的指针
shared
作用范围
value
信号灯初始值
若成功则返回0,否则返回-1。
shared参数:如果shared为0,那么待初始化的信号灯是在
同一进程的各个线程共享的,否则该信号灯是在进程间共享的。
当shared为不零时,该信号灯必须存放在即将使用它的所有进程
都能访问的某种类型的共享内存中。
sem_destroy用于关闭信号灯!
注意:除了sem_open和sem_close外,其它的poisx有名信号灯函数
都可以用于基于内存的信号灯。
>>>>>:
注意与“有名信号灯”的主要区别:
1.sem_open不需要类型与shared的参数,有名信号灯总是可以在不同进程间共享的。
2.注意:对于一个给定的信号灯,我们必须小心保证只调用一次sem_init。
( 当然可以使用pthread_once_t来进行初始化 )
3.posix有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以
通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。posix基于内存的
信号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程
共享的,那么该信号灯就是随进程持续的,当该进程终止时它也会消失。如果某
个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,
这要只要该共享内存区存在,该信号灯就存在。
加锁与不加锁的对比:
//!>不加锁测试
//!>简单的售票小测试程序
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
int
g_ticket =10;
//!> 票数目
void * entrance_1( void * arg )
{
while(g_ticket > 0 )
{
printf("线程1售出票: %d \n", g_ticket);
--g_ticket;
sleep( 1);
}
}
void * entrance_2( void * arg )
{
while(g_ticket > 0 )
{
printf("线程2售出票: %d \n", g_ticket);
--g_ticket;
sleep( 1);
}
}
int main( int argc, char ** argv )
{
pthread_t
tid1,tid2;
if(pthread_create( &tid1, NULL, entrance_1, NULL ) !=0 )
{
printf("创建线程1失败...\n");
exit(EXIT_FAILURE );
}
if(pthread_create( &tid2, NULL, entrance_2, NULL ) !=0 )
{
printf("创建线程2失败...\n");
exit(EXIT_FAILURE );
}
pthread_join( tid1, NULL);
//!> 等待...
pthread_join( tid2, NULL);
//!> 等待...
return0;
}
./test
结果:
线程1售出票:10
线程2售出票:10
线程2售出票:8
线程1售出票:8
线程2售出票:6
线程1售出票:5
线程1售出票:4
线程2售出票:4
线程2售出票:2
线程1售出票:2
看看结果就知道售票是不对的!!!
///***********************************************************************************************************************************************************
//!>加锁测试
//!>简单的售票小测试程序
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
int
g_ticket =10;
//!> 票数目
pthread_mutex_t
g_mutex;
void * entrance_1( void * arg )
{
while(g_ticket > 0 )
{
pthread_mutex_lock( &g_mutex);
//!> 加锁
printf("线程1售出票: %d \n", g_ticket);
--g_ticket;
pthread_mutex_unlock( &g_mutex);
//!> 解锁
sleep( 1);
}
}
void * entrance_2( void * arg )
{
while(g_ticket > 0 )
{
pthread_mutex_lock( &g_mutex);
//!> 加锁
printf("线程2售出票: %d \n", g_ticket);
--g_ticket;
pthread_mutex_unlock( &g_mutex);
//!> 解锁
sleep( 1);
}
}
int main( int argc, char ** argv )
{
pthread_t
tid1,tid2;
pthread_mutex_init( &g_mutex, NULL);
//!> 初始化
if(pthread_create( &tid1, NULL, entrance_1, NULL ) !=0 )
{
printf("创建线程1失败...\n");
exit(EXIT_FAILURE );
}
if(pthread_create( &tid2, NULL, entrance_2, NULL ) !=0 )
{
printf("创建线程2失败...\n");
exit(EXIT_FAILURE );
}
pthread_join( tid1, NULL);
//!> 等待...
pthread_join( tid2, NULL);
//!> 等待...
pthread_mutex_destroy( &g_mutex);
//!> 删除互斥灯
return0;
}
./test
结果:
线程1售出票:10
线程2售出票: 9
线程1售出票:8
线程2售出票:7
线程1售出票:6
线程2售出票:5
线程1售出票:4
线程2售出票:3
线程1售出票:2
线程2售出票:1
//******************************************************************************************************************************************************888
1. 互斥锁:
说白了就是可以是线程之间或者进程之间互斥访问共享资源!
函数:
pthread_mutex_init
初始化一个互斥量
pthread_mutex_lock
给一个互斥量加锁
pthread_mutex_trylock
在锁已经被占据时返回EBUSY而不是挂起
pthread_mutex_unlock
解锁
加锁后只有加锁权限的线程或者进程才可以进行操作,其他的要
访问共享资源就只能是wait等待... ...
关于初始化问题有两种方法:
1.直接静态设置pthread_mutex_t量为PTHREAD_MUTEX_INITIALIZER
2.可以动态设置使用函数pthread_mutex_inti( ... )
2. 初始化和回收互斥锁
#include<pthread.h>
>>>>>:
intpthread_mutex_init( pthread_mutex_t * mutex, const
pthread_mutexattr_t * attr
);
参数:mutex
互斥量
attr
互斥锁属性
若成功则返回0,否则返回错误编号。
>>>>>:
intpthread_mutex_destroy( pthread_mutex_t * mutex);
参数:mutex
互斥量
若成功则返回0,否则返回错误编号。
3. 对互斥量加减锁:
#include<pthread.h>
intpthread_mutex_lock(pthread_mutex_t*mutex);
//!> 加锁
intpthread_mutex_trylock(pthread_mutex_t*mutex);
//!>
//在锁已经被占据时返回EBUSY而不是挂起等
intpthread_mutex_unlock(pthread_mutex_t*mutex);
//!> 去锁
若成功则返回0,否则返回错误编号。
4. 上面的测试代码我们知道对于初始化时的属性都是设置为NULL的,
也就是pthread_mutex_init( &g_mutex, NULL);
但是很多时候是不可以的NULL的,所以下面就是解决mutex属性
pthread_mutexattr_t
#include<pthread.h>
intpthread_mutexattr_init( pthread_mutexattr_t * attr );
//!> 初始化属性
intpthread_mutexattr_destroy( pthread_mutexattr_t * attr );
//!> 回收
若成功返回0,若失败返回错误编号。
注意:
pthread_mutexattr_init将属性对象的值初始化为缺省值。并分配属性对象占用的内
存空间。
注意pshared属性:
它的取值可以是PTHREAD_PROCESS_PRIVATE(缺省值,表示由这个属性对象创建
的互斥锁只能在进程内使用)或PTHREAD_PROCESS_SHARED。
互斥量属性分为共享互斥量属性和类型互斥量属性。
>>>>>:
pthread_mutexattr_getpshared和pthread_mutexattr_setpshared函数可以获得和修
改共享互斥量属性。
>>>>>:
pthread_mutexattr_gettype和pthread_mutexattr_settype函数可以获得和修改类型互
斥量属性。
5. 获得/修改共享互斥量属性:
#include<pthread.h>
intpthread_mutexattr_getpshared(const pthread_mutexattr_t *restrictattr, int
*restrictshared );
//!> 获得共享互斥量属性,由shared带出
intpthread_mutexattrattr_ setpshared (
constpthread_mutexattr_t *restrict attr,int
pshared);
//!> 设置共享互斥属性,有shard决定
若成功返回0,若失败返回错误编号。
注意:shared的取值可以是
PTHREAD_PROCESS_SHARED
PTHREAD_PROCESS_PRIVATE
附录:如果互斥锁属性对象的pshared属性被置PTHREAD_PROCESS_SHARED。
那么由这个属性对象创建的互斥锁将被保存在共享内存中,可以被多个进程中的
线程共享。如果pshared属性被置为PTHREAD_PROCESS_PRIVATE,那么只有和
创建这个互斥锁的线程在同一个进程中的线程才能访问这个互斥锁。
6. 获得/修改类型互斥量属性
#include<pthread.h>
#intpthread_mutexattr_settype (pthread_mutexattr_t * attr, intkind);
//!> 设置类型互斥类型
#intpthread_mutexattr_gettype (pthread_mutexattr_t * attr, int *kind);
//!> 获得类型互斥属性
若成功返回0,若失败返回错误编号。
类型:
缺省的互斥锁类型属性是:
PTHREAD_MUTEX_DEFAULT。
合法的类型属性值有:
PTHREAD_MUTEX_NORMAL:
>: 这种类型的互斥锁不会自动检测死锁。
>: 如果一个线程试图对一个互斥锁重复锁定,将会引起这个线程的死锁。
>: 如果试图解锁一个由别的线程锁定的互斥锁会引发不可预料的结果。
>: 如果一个线程试图解锁已经被解锁的互斥锁也会引发不可预料的结果。
PTHREAD_MUTEX_ERRORCHECK:
>: 这种类型的互斥锁会自动检测死锁。
>: 如果一个线程试图对一个互斥锁重复锁定,将会返回一个错误代码。
>: 如果试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。
>: 如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。
PTHREAD_MUTEX_RECURSIVE:
>: 如果一个线程对这种类型的互斥锁重复上锁,不会引起死锁。
>: 一个线程对这类互斥锁的多次重复上锁必须由这个线程来重复相同数量的解
锁,这样才能解开这个互斥锁,别的线程才能得到这个互斥锁。
>: 如果试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。
>: 如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。
>: 这种类型的互斥锁只能是进程私有的(作用域属性为
PTHREAD_PROCESS_PRIVATE)
PTHREAD_MUTEX_DEFAULT:
>: 这种类型的互斥锁不会自动检测死锁。
>: 如果一个线程试图对一个互斥锁重复锁定,将会引起不可预料的结果。
>: 如果试图解锁一个由别的线程锁定的互斥锁会引发不可预料的结果。
>: 如果一个线程试图解锁已经被解锁的互斥锁也会引发不可预料的结果。
>: POSIX标准规定,对于某一具体的实现,可以把这种类型的互斥锁定义为其他
类型的互斥锁。
相当注意:
1、互斥量需要时间来加锁和解锁。锁住较少互斥量的程序通常运行得更快。所
以,互斥量应该尽量少,够用即可,每个互斥量保护的区域应则尽量大。
2、互斥量的本质是串行执行。如果很多线程需要领繁地加锁同一个互斥量,则
线程的大部分时间就会在等待,这对性能是有害的。如果互斥量保护的数
据(或代码)包含彼此无关的片段,则可以特大的互斥量分解为几个小的互斥量
来提高性能。这样,任意时刻需要小互斥量的线程减少,线程等待时间就会
减少。所以,互斥量应该足够多(到有意义的地步),每个互斥量保护的区域则
应尽量的少。
3、POSIX线程锁机制的Linux实现都不是取消点,因此,延迟取消类型的线程不
会因收到取消信号而离开加锁等待。
4、线程在加锁后解锁前被取消,锁将永远保持锁定状态。因此如果在关键区段
内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解
锁。
5、锁机制不是异步信号安全的,也就是说,不应该在信号处理过程中使用互斥
锁,否则容易造成死锁。