线程最大的特点就是资源的共享性,所以也就有了一个难点线程同步,实现线程同步的方法最常用的方法是:互斥锁,条件变量和信号量。接下来就让我们来看下这几种同步的方法。
一、互斥锁(Mutex)
获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。
1、锁的初始化和销毁(Mutex用pthread_mutex_t类型的变量表示)
<a href="https://s4.51cto.com/wyfs02/M02/A7/6C/wKioL1nmpo_Swv8XAABm7CySx6g673.png" target="_blank"></a>
注意:如果Mutex变量是静态分配的(全局变量 或static变量),也可以用宏PTHREAD_MUTEX_INITIALIZER来初始化,相当于用pthread_mutex_init初始化并且attr参数为NULL。用函数初始化则是动态分配。
2、加锁解锁
<a href="https://s5.51cto.com/wyfs02/M01/A7/6C/wKioL1nmp7GwBHHHAABTcKT1dPo031.png" target="_blank"></a>
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用
pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
<code>#include<stdio.h></code>
<code>#include<stdlib.h></code>
<code>#include<pthread.h></code>
<code>#define LOOP 5000</code>
<code>static</code> <code>int</code> <code>count = 0;</code>
<code>pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;</code>
<code>void</code> <code>*read_write(</code><code>void</code> <code>*val)</code>
<code>{</code>
<code> </code><code>int</code> <code>_val = 0;</code>
<code> </code><code>int</code> <code>i = 0;</code>
<code> </code><code>for</code><code>( ; i < LOOP; i++ ){</code>
<code> </code><code>pthread_mutex_lock(&mutex_lock);</code>
<code> </code><code>_val = count;</code>
<code> </code><code>printf</code><code>(</code><code>"pthread id is :%x,count : %d\n"</code><code>,</code>
<code> </code><code>(unsigned </code><code>long</code><code>)pthread_self(),count);</code>
<code> </code><code>count = _val+1;</code>
<code> </code><code>pthread_mutex_unlock(&mutex_lock);</code>
<code> </code><code>}</code>
<code>}</code>
<code>int</code> <code>main()</code>
<code> </code><code>pthread_t tid1;</code>
<code> </code><code>pthread_t tid2;</code>
<code> </code><code>pthread_create(&tid1,NULL,read_write,NULL);</code><code>//为了简单没有判断是否创建成功</code>
<code> </code><code>pthread_create(&tid2,NULL,read_write,NULL);</code>
<code> </code><code>pthread_join(tid1,NULL);</code>
<code> </code><code>pthread_join(tid2,NULL);</code>
<code> </code><code>printf</code><code>(</code><code>"count final value is : %d\n"</code><code>,count);</code>
<code> </code><code>return</code> <code>0;</code>
如果没有加入互斥锁,运行结果不会是10000。
3、互斥锁的缺点
死锁的情况:1)一般情况下,如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了。2)线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了。
解决办法:首先要尽量避免同时获得多个锁。如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死锁。
二、条件变量
互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
1、条件变量的初始化和销毁
<a href="https://s5.51cto.com/wyfs02/M00/08/BE/wKiom1nms0jBDzejAABgwD25S88063.png" target="_blank"></a>
条件变量初始化和互斥锁初始化类似,宏表示静态分配,函数表示动态分配,当函数的第二个参数为NULL则两者等价。
2、等待条件成立和激活条件变量
<a href="https://s3.51cto.com/wyfs02/M00/08/BE/wKiom1nmtcWTdwg6AAByOUDxKcs887.png" target="_blank"></a>
<a href="https://s1.51cto.com/wyfs02/M00/A7/6E/wKioL1nmskCzx6YlAAA7Ziy_vK0848.png" target="_blank"></a>
可见,一个条件变量总是和一个Mutex搭配使用的。 一个线程可以调用pthread_cond_wait在一个条件变量上阻塞等待,这个函数做以下三步操作:
1)释放Mutex
2)阻塞等待
3)当被唤醒时,重新获得Mutex并返回
pthread_cond_timedwait函数还有一个额外的参数可以设定等待超时,如果到达了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。一个线程可以调用pthread_cond_signal唤醒在某个条件变量上等待的另一个线程,也可以调用pthread_cond_broadcast唤醒在这个条件变量上等待的所有线程。
下面写一个生产者和消费者的实现(链表):
<code>typedef</code> <code>struct</code> <code>list{</code>
<code> </code><code>struct</code> <code>list *next;</code>
<code> </code><code>int</code> <code>val;</code>
<code>}product_list;</code>
<code>product_list *head = NULL;</code>
<code>static</code> <code>pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;</code>
<code>static</code> <code>pthread_cond_t need_product=PTHREAD_COND_INITIALIZER;</code>
<code>/*init list*/</code>
<code>void</code> <code>init_list(product_list *list)</code>
<code> </code><code>if</code><code>(NULL != list){</code>
<code> </code><code>list->next = NULL;</code>
<code> </code><code>list->val = 0;</code>
<code>/*consumer*/</code>
<code>void</code> <code>*consumer(</code><code>void</code> <code>*_val)</code>
<code> </code><code>product_list *p = NULL;</code>
<code> </code><code>while</code><code>(1){</code>
<code> </code><code>pthread_mutex_lock(&lock);</code>
<code> </code><code>while</code><code>(NULL == head){</code>
<code> </code><code>pthread_cond_wait(&need_product,&lock);</code>
<code> </code><code>}</code>
<code> </code><code>p = head;</code>
<code> </code><code>head = head->next;</code>
<code> </code><code>p->next = NULL;</code>
<code> </code><code>pthread_mutex_unlock(&lock);</code>
<code> </code><code>printf</code><code>(</code><code>"consum success,val is : %d\n"</code><code>,p->val);</code>
<code> </code><code>free</code><code>(p);</code>
<code> </code><code>p = NULL;</code>
<code>/*product*/</code>
<code>void</code> <code>*product(</code><code>void</code> <code>*_val)</code>
<code> </code><code>sleep(</code><code>rand</code><code>()%2);</code>
<code> </code><code>product_list *p = (product_list*)</code><code>malloc</code><code>(</code><code>sizeof</code><code>(product_list));</code>
<code> </code><code>init_list(p);</code>
<code> </code><code>p->val = </code><code>rand</code><code>()%1000;</code>
<code> </code><code>head = p;</code>
<code> </code><code>printf</code><code>(</code><code>"product success,val : %d\n"</code><code>,p->val);</code>
<code> </code><code>pthread_cond_signal(&need_product);</code>
<code> </code><code>pthread_t t_product;</code>
<code> </code><code>pthread_t t_consumer;</code>
<code> </code><code>pthread_create(&t_product,NULL,product,NULL);</code><code>//no check</code>
<code> </code><code>pthread_create(&t_consumer,NULL,consumer,NULL);</code><code>//no check</code>
<code> </code><code>pthread_join(t_product,NULL);</code><code>//wait product thread</code>
<code> </code><code>pthread_join(t_consumer,NULL);</code><code>//wait consumer thread</code>
<a href="https://s3.51cto.com/wyfs02/M01/A7/6F/wKioL1nmvBqzoRzKAABvD0kOj9U458.png" target="_blank"></a>
三、信号量
Mutex变量是非0即1的,可看作一种资源的可用数量,初始化时Mutex是1,表示有一个可用资源,加锁时获得该资源,将Mutex减到0,表示不再有可用资源,解锁时释放该资源,将Mutex重新加到1,表示又有了一个可用资源。
信号量(Semaphore)和Mutex类似,表示可用资源的数量,和Mutex不同的是这个数量可以大于1。即,如果信号量描述的资源数目是1时,此时的信号量和互斥锁相同!POSIX semaphore库函数,这种信号量不仅可用于同一进程的线程间同步,也可用于不同进程间的同步。
1、信号量的创建和销毁
<code>int</code> <code>sem_init(sem_t *sem , </code><code>int</code> <code>pshared, unsigned </code><code>int</code> <code>value);</code>
<code>int</code> <code>sem_destroy(sem_t *sem);</code>
2、信号量的等待和释放
<code>int</code> <code>sem_wait(sem_t *sem);</code><code>//阻塞等待</code>
<code>int</code> <code>sem_trywait(sem_t *sem);</code><code>//非阻塞等待</code>
调用sem_wait()可以获得资源(P操作),使semaphore的值减1,如果调用sem_wait()时semaphore的值已经是0,则挂起等待。如果不希望挂起等待,可以调用sem_trywait() 。调用sem_post() 可以释放资源(V操作),使semaphore 的值加1,同时唤醒挂起等待的线程。
下面写一个生产者和消费者的实现(固定大小循环队列):
<code>#include<semaphore.h></code>
<code>#define SEM_PRO 10</code>
<code>#define SEM_COM 0</code>
<code>/*定义信号量*/</code>
<code>sem_t sem_product;</code>
<code>sem_t sem_consume;</code>
<code>int</code> <code>bank[SEM_PRO];</code>
<code>/*消费者线程执行函数*/</code>
<code> </code><code>int</code> <code>c = 0;</code>
<code> </code><code>sem_wait(&sem_consume);</code>
<code> </code><code>int</code> <code>_consume = bank[c];</code>
<code> </code><code>printf</code><code>(</code><code>"consumer done...,val : %d\n"</code><code>,_consume);</code>
<code> </code><code>sem_post(&sem_product);</code>
<code> </code><code>c = (c+1)%SEM_PRO;</code>
<code> </code><code>sleep(</code><code>rand</code><code>()%5);</code>
<code>/*生产者线程执行函数*/</code>
<code>void</code> <code>*producter(</code><code>void</code> <code>*_val)</code>
<code> </code><code>int</code> <code>p = 0;</code>
<code> </code><code>sem_wait(&sem_product);</code>
<code> </code><code>int</code> <code>_product = </code><code>rand</code><code>()%100;</code>
<code> </code><code>bank[p] = _product;</code>
<code> </code><code>printf</code><code>(</code><code>"product done... val is : %d\n"</code><code>,_product);</code>
<code> </code><code>sem_post(&sem_consume);</code>
<code> </code><code>p = (p+1)%SEM_PRO;</code>
<code> </code><code>sleep(</code><code>rand</code><code>()%3);</code>
<code>/*创建生产者消费者线程*/</code>
<code>void</code> <code>run_product_consume()</code>
<code> </code><code>pthread_t tid_consumer;</code>
<code> </code><code>pthread_t tid_producter;</code>
<code> </code><code>pthread_create(&tid_consumer,NULL,consumer,NULL);</code>
<code> </code><code>pthread_create(&tid_producter,NULL,producter,NULL);</code>
<code> </code>
<code> </code><code>pthread_join(tid_consumer,NULL);</code>
<code> </code><code>pthread_join(tid_producter,NULL);</code>
<code>/*销毁信号量*/</code>
<code>void</code> <code>destroy_all_sem()</code>
<code> </code><code>printf</code><code>(</code><code>"process done...\n"</code><code>);</code>
<code> </code><code>sem_destroy(&sem_product);</code>
<code> </code><code>sem_destroy(&sem_consume);</code>
<code> </code><code>exit</code><code>(0);</code>
<code>/*初始化信号量*/</code>
<code>void</code> <code>init_all_sem()</code>
<code> </code><code>int</code> <code>bank[SEM_PRO];</code>
<code> </code><code>int</code> <code>num = </code><code>sizeof</code><code>(bank)/</code><code>sizeof</code><code>(bank[0]);</code>
<code> </code><code>for</code><code>(; i < num; i++ ){</code>
<code> </code><code>bank[i] = 0;</code>
<code> </code><code>sem_init(&sem_product,0,SEM_PRO);</code>
<code> </code><code>sem_init(&sem_consume,0,SEM_COM);</code>
<code> </code><code>init_all_sem();</code>
<code> </code><code>run_product_consume();</code>
<code>// destroy_all_sem();//避免销毁信号量</code>
<a href="https://s5.51cto.com/wyfs02/M02/A7/71/wKioL1nmyyTDE2GvAABrfUyaGA8668.png" target="_blank"></a>
本文转自 8yi少女的夢 51CTO博客,原文链接:http://blog.51cto.com/zhaoxiaohu/1973694,如需转载请自行联系原作者