1. 线程的同步与互斥

1.1. 线程的互斥

在Posix Thread中定义了一套专门用于线程互斥的mutex函数。mutex是一种简单的加锁的方法来控制对共享资源的存取,这个互斥锁只有两种状态(上锁和解锁),可以把互斥锁看作某种意义上的全局变量。为什么需要加锁,就是因为多个线程共用进程的资源,要访问的是公共区间时(全局变量),当一个线程访问的时候,需要加上锁以防止另外的线程对它进行访问,实现资源的独占。在一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。

1. 创建和销毁锁

有两种方法创建互斥锁,静态方式和动态方式。

·静态方式:

POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

在Linux Threads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个宏常量。

·动态方式:

动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)

其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。通常为NULL

pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此Linux Threads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。

2. 互斥锁属性

互斥锁属性结构体的定义为:

typedef struct

{

int __mutexkind; //注意这里是两个下划线

} pthread_mutexattr_t;

互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性__mutexkind,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同也就是是否阻塞等待。有三个值可供选择:

·PTHREAD_MUTEX_TIMED_NP,这是缺省值(直接写NULL就是表示这个缺省值),也就是普通锁(或快速锁)。当一个线程加锁以后,其余请求锁的线程将形成一个阻塞等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性

示例:初始化一个快速锁。

pthread_mutex_t lock;

pthread_mutex_init(&lock, NULL);

·PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

示例:初始化一个嵌套锁。

pthread_mutex_t lock;

pthread_mutexattr_t mutexattr;

mutexattr.__mutexkind = PTHREAD_MUTEX_RECURSIVE_NP;

pthread_mutex_init(&lock, &mutexattr);

·PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。如果锁的类型是快速锁,一个线程加锁之后,又加锁,则此时就是死锁。

示例:初始化一个嵌套锁。

pthread_mutex_t lock;

pthread_mutexattr_t mutexattr;

mutexattr.__mutexkind = PTHREAD_MUTEX_ERRORCHECK_NP;

pthread_mutex_init(&lock, &mutexattr);

3.锁操作

锁操作主要包括

加锁 int pthread_mutex_lock(pthread_mutex_t *mutex)

解锁 int pthread_mutex_unlock(pthread_mutex_t *mutex)

测试加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex)

·pthread_mutex_lock:加锁,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。

·pthread_mutex_unlock:根据不同的锁类型,实现不同的行为:

对于快速锁,pthread_mutex_unlock解除锁定;

对于递规锁,pthread_mutex_unlock使锁上的引用计数减1;

对于检错锁,如果锁是当前线程锁定的,则解除锁定,否则什么也不做。

·pthread_mutex_trylock:语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。

Example:比较pthread_mutex_trylock()与pthread_mutex_lock()

#include <stdio.h>

#include <pthread.h>

pthread_mutex_t lock;

void* pthfunc(void *args)

{

pthread_mutex_lock(&lock); //先加一次锁

pthread_mutex_lock(&lock); //再用lock加锁,会挂起阻塞

//pthread_mutex_trylock(&lock); //用trylock加锁,则不会挂起阻塞

printf("hello\n");

sleep(1);

pthread_exit(NULL);

}

main()

{

pthread_t pthid = 0;

pthread_mutex_init(&lock,NULL);

pthread_create(&pthid,NULL,pthfunc,NULL);

pthread_join(pthid,NULL);

pthread_mutex_destroy(&lock);

}

4. 加锁注意事项

如果线程在加锁后解锁前被取消,锁将永远保持锁定状态,因此如果在关键区段内有取消点存在,则必须在退出回调函数pthread_cleanup_push/pthread_cleanup_pop中解锁。同时不应该在信号处理函数中使用互斥锁,否则容易造成死锁。

5. 互斥锁实例

Example:火车站售票(此处不加锁,则会出现卖出负数票的情况)

#include <stdio.h>

#include <pthread.h>

int ticketcount = 20; //火车票,公共资源(全局)

void* salewinds1(void* args) //售票口1

{

while(ticketcount > 0) //如果有票,则卖票

{

printf("windows1 start sale ticket!the ticket is:%d\n",ticketcount);

sleep(3); //卖一张票需要3秒的操作时间

ticketcount --; //出票

printf("sale ticket finish!,the last ticket is:%d\n",ticketcount);

}

}

void* salewinds2(void* args) //售票口2

{

while(ticketcount > 0) //如果有票,则卖票

{

printf("windows2 start sale ticket!the ticket is:%d\n",ticketcount);

sleep(3); //卖一张票需要3秒的操作时间

ticketcount --; //出票

printf("sale ticket finish!,the last ticket is:%d\n",ticketcount);

}

}

int main()

{

pthread_t pthid1 = 0;

pthread_t pthid2 = 0;

pthread_create(&pthid1,NULL,salewinds1,NULL); //线程1

pthread_create(&pthid2,NULL,salewinds2,NULL); //线程2

pthread_join(pthid1,NULL);

pthread_join(pthid2,NULL);

return 0;

}

Example:加锁之后的火车售票

#include <stdio.h>

#include <pthread.h>

int ticketcount = 20;

pthread_mutex_t lock;

void* salewinds1(void* args)

{

while(1)

{

pthread_mutex_lock(&lock); //因为要访问全局的共享变量,所以就要加锁

if(ticketcount > 0) //如果有票

{

printf("windows1 start sale ticket!the ticket is:%d\n",ticketcount);

sleep(3); //卖一张票需要3秒的操作时间

ticketcount --; //出票

printf("sale ticket finish!,the last ticket is:%d\n",ticketcount);

}

else //如果没有票

{

pthread_mutex_unlock(&lock); //解锁

pthread_exit(NULL); //退出线程

}

pthread_mutex_unlock(&lock); //解锁

sleep(1); //要放到锁的外面,让另一个有时间锁

}

}

void* salewinds2(void* args)

{

while(1)

{

pthread_mutex_lock(&lock); //因为要访问全局的共享变量,所以就要加锁

if(ticketcount>0) //如果有票

{

printf("windows2 start sale ticket!the ticket is:%d\n",ticketcount);

sleep(3); //卖一张票需要3秒的操作时间

ticketcount --; //出票

printf("sale ticket finish!,the last ticket is:%d\n",ticketcount);

}

else //如果没有票

{

pthread_mutex_unlock(&lock); //解锁

pthread_exit(NULL); //退出线程

}

pthread_mutex_unlock(&lock); //解锁

sleep(1); //要放到锁的外面,让另一个有时间锁

}

}

int main()

{

pthread_t pthid1 = 0;

pthread_t pthid2 = 0;

pthread_mutex_init(&lock,NULL); //初始化锁

pthread_create(&pthid1,NULL,salewinds1,NULL); //线程1

pthread_create(&pthid2,NULL,salewinds2,NULL); //线程2

pthread_join(pthid1,NULL);

pthread_join(pthid2,NULL);

pthread_mutex_destroy(&lock); //销毁锁

return 0;

}

总结:线程互斥mutex:加锁步骤如下:

1. 定义一个全局的pthread_mutex_t lock; 或者用

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //则main函数中不用init

2. 在main中调用 pthread_mutex_init函数进行初始化

3. 在子线程函数中调用pthread_mutex_lock加锁

4. 在子线程函数中调用pthread_mutex_unlock解锁

5. 最后在main中调用 pthread_mutex_destroy函数进行销毁

1.2. 线程的同步

6 条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变量的条件成立而挂起;另一个线程使条件成立(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

1、 创建和注销

条件变量和互斥锁一样,都有静态、动态两种创建方式:

静态方式使PTHREAD_COND_INITIALIZER常量,如下:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

动态方式调用pthread_cond_init()函数,API定义如下:

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

尽管POSIX标准中为条件变量定义了属性,但在Linux Threads中没有实现,因此cond_attr值通常为NULL,且被忽略。

注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:

int pthread_cond_destroy(pthread_cond_t *cond);

2、 等待和激发

等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait():

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

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

线程解开mutex指向的锁并被条件变量cond阻塞。其中计时等待方式表示经历abstime段时间后,即使条件变量不满足,阻塞也被解除。无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。(也就是说在做pthread_cond_wait之前,往往要用pthread_mutex_lock进行加锁,而调用pthread_cond_wait函数会将锁解开,然后将线程挂起阻塞。直到条件被pthread_cond_signal激发,再将锁状态恢复为锁定状态,最后再用pthread_mutex_unlock进行解锁)。

激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程

3、 其他

pthread_cond_wait()和pthread_cond_timedwait()都被实现为取消点,也就是说如果pthread_cond_wait()被取消,则退出阻塞,然后将锁状态恢复,则此时mutex是保持锁定状态的,而当前线程已经被取消掉,那么解锁的操作就会得不到执行,此时锁得不到释放,就会造成死锁,因而需要定义退出回调函数来为其解锁。

以下示例集中演示了互斥锁和条件变量的结合使用,以及取消对于条件等待动作的影响。在例子中,有两个线程被启动,并等待同一个条件变量,如果不使用退出回调函数(见范例中的注释部分),则tid2将在pthread_mutex_lock()处永久等待。如果使用回调函数,则tid2的条件等待及主线程的条件激发都能正常工作。

实例:

#include <stdio.h>

#include <pthread.h>

#include <unistd.h>

pthread_mutex_t mutex;

pthread_cond_t cond;

void ThreadClean(void *arg)

{

pthread_mutex_unlock(&mutex);

}

void * child1(void *arg)

{

//pthread_cleanup_push(ThreadClean,NULL); //1

while(1){

printf("thread 1 get running \n");

printf("thread 1 pthread_mutex_lock returns %d\n", pthread_mutex_lock(&mutex));

pthread_cond_wait(&cond,&mutex); //等待父进程发送信号

printf("thread 1 condition applied\n");

pthread_mutex_unlock(&mutex);

sleep(5);

}

//pthread_cleanup_pop(0); //2

return 0;

}

void *child2(void *arg)

{

while(1){

sleep(3); //3

printf("thread 2 get running.\n");

printf("thread 2 pthread_mutex_lock returns %d\n", pthread_mutex_lock(&mutex));

pthread_cond_wait(&cond,&mutex);

printf("thread 2 condition applied\n");

pthread_mutex_unlock(&mutex);

sleep(1);

}

}

int main(void)

{

pthread_t tid1,tid2;

printf("hello, condition variable test\n");

pthread_mutex_init(&mutex,NULL);

pthread_cond_init(&cond,NULL);

pthread_create(&tid1,NULL,child1,NULL);

pthread_create(&tid2,NULL,child2,NULL);

while(1){ //父线程

sleep(2); //4

pthread_cancel(tid1); //5

sleep(2); //6

pthread_cond_signal(&cond);

}

sleep(10);

return 0;

}

不做注释1,2则导致child1中的unlock得不到执行,锁一直没有关闭,而child2中的锁不能执行lock,则会一直在pthread_mutex_lock()处永久等待。如果不做注释5的pthread_cancel()动作,即使没有那些sleep()延时操作,child1和child2都能正常工作。注释3和注释4的延迟使得child1有时间完成取消动作,从而使child2能在child1退出之后进入请求锁操作。如果没有注释1和注释2的回调函数定义,系统将挂起在child2请求锁的地方,因为child1没有释放锁;而如果同时也不做注释3和注释4的延时,child2能在child1完成取消动作以前得到控制,从而顺利执行申请锁的操作,但却可能挂起在pthread_cond_wait()中,因为其中也有申请mutex的操作。child1函数给出的是标准的条件变量的使用方式:回调函数保护,等待条件前锁定,pthread_cond_wait()返回后解锁。

条件变量机制和互斥锁一样,不能用于信号处理中,在信号处理函数中调用pthread_cond_signal()或者pthread_cond_broadcast()很可能引起死锁。

Example:火车售票,利用条件变量,当火车票卖完的时候,再重新设置票数为10;

#include<pthread.h>

#include<stdio.h>

int ticketcount = 10;

pthread_mutex_t lock; //互斥锁

pthread_cond_t cond; //条件变量

void* salewinds1(void* args)

{

while(1)

{

pthread_mutex_lock(&lock); //因为要访问全局的共享变量ticketcount,所以就要加锁

if(ticketcount > 0) //如果有票

{

printf("windows1 start sale ticket!the ticket is:%d\n",ticketcount);

ticketcount --;//则卖出一张票

if(ticketcount == 0)

pthread_cond_signal(&cond); //通知没有票了

printf("sale ticket finish!,the last ticket is:%d\n",ticketcount);

}

else //如果没有票了,就解锁退出

{

pthread_mutex_unlock(&lock);

break;

}

pthread_mutex_unlock(&lock);

sleep(1); //要放到锁的外面

}

}

void* salewinds2(void* args)

{

while(1)

{

pthread_mutex_lock(&lock);

if(ticketcount > 0)

{

printf("windows2 start sale ticket!the ticket is:%d\n",ticketcount);

ticketcount --;

if(ticketcount == 0)

pthread_cond_signal(&cond); //发送信号

printf("sale ticket finish!,the last ticket is:%d\n",ticketcount);

}

else

{

pthread_mutex_unlock(&lock);

break;

}

pthread_mutex_unlock(&lock);

sleep(1);

}

}

void *setticket(void *args) //重新设置票数

{

pthread_mutex_lock(&lock); //因为要访问全局变量ticketcount,所以要加锁

if(ticketcount > 0)

pthread_cond_wait(&cond,&lock); //如果有票就解锁并阻塞,直到没有票就执行下面的

ticketcount = 10; //重新设置票数为10

pthread_mutex_unlock(&lock); //解锁

sleep(1);

pthread_exit(NULL);

}

main()

{

pthread_t pthid1,pthid2,pthid3;

pthread_mutex_init(&lock,NULL); //初始化锁

pthread_cond_init(&cond,NULL); //初始化条件变量

pthread_create(&pthid1,NULL, salewinds1,NULL); //创建线程

pthread_create(&pthid2,NULL, salewinds2,NULL);

pthread_create(&pthid3,NULL, setticket,NULL);

pthread_join(pthid1,NULL); //等待子线程执行完毕

pthread_join(pthid2,NULL);

pthread_join(pthid3,NULL);

pthread_mutex_destroy(&lock); //销毁锁

pthread_cond_destroy(&cond); //销毁条件变量

}

Linux多线程(三)(同步互斥)的更多相关文章

  1. 多线程(三) 同步synchronized

    五.同步 1.锁 多线程程序一般是为了完成一些相同的工作而存在的,因此有时间也会共享一些资源,例如对象.变量等等,此时如果不对各个线程进行资源协调,就会出现一些冲突,从而导致程序功能失效.例如下面的示 ...

  2. 【Linux多线程】同步与互斥的区别

    同步与互斥这两个概念经常被混淆,所以在这里说一下它们的区别. 一.同步与互斥的区别 1. 同步 同步,又称直接制约关系,是指多个线程(或进程)为了合作完成任务,必须严格按照规定的 某种先后次序来运行. ...

  3. Linux多线程与同步

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 典型的UNIX系统都支持一个进程创建多个线程(thread).在Linux进程基础 ...

  4. linux多线程编程之互斥锁

    多线程并行运行,共享同一种互斥资源时,需要上互斥锁来运行,主要是用到pthread_mutex_lock函数和pthread_mutex_unlock函数对线程进行上锁和解锁 下面是一个例子: #in ...

  5. Linux多线程之同步2 —— 生产者消费者模型

    思路 生产者和消费者(互斥与同步).资源用队列模拟(要上锁,一个时间只能有一个线程操作队列). m个生产者.拿到锁,且产品不满,才能生产.当产品满,则等待,等待消费者唤醒.当产品由空到不空,通知消费者 ...

  6. Linux多线程之同步

    引言 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变量的条件成立而挂起(此时不再占用cpu):另一个线程使条件成立(给出条件成立信号).为了防止竞争,条件变 ...

  7. Windows下多线程数据同步互斥的有关知识

     对于操作系统而言,在并行程序设计中难免会遇到数据同步和共享的问题,本文针对这个问题,以windows系统为例回顾一下资源同步的相关问题.要点如下: 1.同步和数据共享  数据征用 2.同步原语 ...

  8. php Pthread 多线程 (三) Mutex 互斥量

    当我们用多线程操作同一个资源时,在同一时间内只能有一个线程能够对资源进行操作,这时就需要用到互斥量了.比如我们对同一个文件进行读写操作时. <?php class Add extends Thr ...

  9. Linux多线程之同步3

    需求 客户端将需要解决的task发送给服务器,服务器调用线程来解决客户端发送的task,解决完由线程负责将其发送回客户端.(用管道实现通信) 思路 1. server维护两个列表.一是客户端列表.二是 ...

  10. linux 进程间同步互斥

    参考链接: https://www.oschina.net/code/snippet_237505_8646 http://www.cnblogs.com/xilentz/archive/2012/1 ...

随机推荐

  1. Topcoder srm 632 div2

    脑洞太大,简单东西就是想复杂,活该一直DIV2; A:水,基本判断A[I]<=A[I-1],ANS++; B:不知道别人怎么做的,我的是100*N*N;没办法想的太多了,忘记是连续的数列 我们枚 ...

  2. O2O模式成功案例分享 汲取精华化为己用

    本文通过分享一些公司的o2o成功案例让您了解什么是O2O,o2o的优势,o2o模式有哪些,未来我们要如何做o2o才更有竞争力,学牛人的o2o创新玩法,摸索适合自己的o2o思路.拥抱o2o - 传统企业 ...

  3. 深入理解jQuery的Event机制

    jQuery的Event模块非常强大.其功能远远比原生事件监听器强大许多,对同一个元素的监听只用一个eventListener,内部则是一个强大的观察者,根据匹配事件类型触发相应回调.jQuery不仅 ...

  4. 初步体验javascript try catch机制

    javascript在ECMAScript3中引入了try catch finally机制,大致原理和其他语言一样. 我们也可以自定义错误事件. 但是事先声明:我们自定义的错误事件,只支持对name. ...

  5. CodeForces 1B Spreadsheets (字符串处理,注意细节,大胆尝试)

    题目 注意模后余数为0时,要把除以26后的新数据减1,为什么这样,要靠大胆尝试.我在对小比赛中坑了一下午啊,直到比赛结束也没写出这道题....要死了.. #include<stdio.h> ...

  6. SQL技术内幕-5 比较特殊 insert into 数据的写法

    ---比较特殊,第一次看到这种写法,记录下来 create table Student --学生成绩表 ( id int, --主键 Grade int, --班级 Score int --分数 ) ...

  7. java 如何从配置文件(.properties)中读取内容

    1.如何创建.properties文件 很简单,建立一个txt文件,并把后缀改成.properties即可 2.将.properties文件拷入src的根目录下 3..properties文件内容格式 ...

  8. adt导入已经存在于workspace中的项目

    场景: Eclipse中某android项目被delete,但是并未勾选“delete project contents from disk(cannot be undone)”.删除后,下次再想打开 ...

  9. VCC、VDD、VEE、VSS等有关电源标注的区别

    Almost all integrated circuits (ICs) have at least two pins which connect to the power rails of the ...

  10. Android隐式启动匹配:action,category,data

    简介 Android开发中,Activity,Service 和 BroadcastReceiver 启动有两种方式,显示启动和隐式启动. 为方便下面描述,我以Activity启动为例. 显示启动便是 ...