一、互斥锁
在多线程编程中,互斥锁(Mutex,全名为 Mutual Exclusion)是一种同步机制,用于确保在任意时刻只有一个线程能够访问共享资源,从而避免数据竞争和不确定性的结果。互斥锁提供了一种方式,使得一段关键代码(临界区)在同一时刻只能被一个线程执行。
1.初始化互斥锁
(1)静态方式
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
(2)动态方式
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
2.加锁
pthread_mutex_lock(&mutex);
如果互斥锁已被其他线程锁定,调用线程将被阻塞,直到互斥锁被解锁。
3.解锁
pthread_mutex_lock(&mutex);
解锁后,其他线程就可以获取该互斥锁。
4.销毁锁
pthread_mutex_destroy(&mutex);
现在修改上篇文章中的代码,通过锁的机制解决资源共享问题:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>void *thread_worker1(void *args);
void *thread_worker2(void *args);/*因为创建线程给线程执行函数传参时只能传一个参数,但是我们要传递共享的变量shared_var和lock所以要定义一个结构体(worker_ctx_t,ctx:context)将他们封装在一块传进去*/
typedef struct worker_ctx_s
{
int shared_var;
pthread_mutex_t lock;
}worker_ctx_t;
int main(int argc,char **argv)
{
worker_ctx_t worker_ctx;//使用结构体类型定义了传给子线程的变量参数
pthread_t tid;
pthread_attr_t thread_attr;worker_ctx.shared_var = 1000;
/*对锁进行初始化*/
pthread_mutex_init(&worker_ctx.lock,NULL);
if( pthread_attr_init(&thread_attr) )
{
printf("pthread_attr_init() failure : %s\n",strerror(errno));
return -1;
}if( pthread_attr_setstacksize(&thread_attr,120*1024) )
{
printf("pthread_attr_setstacksize() failure : %s\n",strerror(errno));
return -1;
}if( pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED) )
{
printf("pthread_attr_setdetachstate() failure : %s\n",strerror(errno));
return -1;
}pthread_create(&tid,&thread_attr,thread_worker1,&worker_ctx);
printf("Thread worker1 tid[%ld] created ok\n",tid);pthread_create(&tid,&thread_attr,thread_worker2,&worker_ctx);
printf("Thread worker2 tid[%ld] created ok\n",tid);while(1)
{
printf("Main/Control thread shared_var : %d\n",worker_ctx.shared_var);
sleep(10);
}/*互斥锁在使用完成后,将锁摧毁释放*/
pthread_mutex_destroy(&worker_ctx.lock);
}
void *thread_worker1(void *args)
{
worker_ctx_t *ctx = (worker_ctx_t *)args;if( !args )
{
printf("%s() get invalid arguments\n",__FUNCTION__);
pthread_exit(NULL);
}
printf("Thread worker1 [%ld] start running...\n",pthread_self());while(1)
{
pthread_mutex_lock(&ctx -> lock);printf("+++ : %s before shared_var++ : %d\n",__FUNCTION__,ctx -> shared_var);
ctx->shared_var ++;
sleep(2);
printf("+++ : %s after sleep shared_var : %d\n",__FUNCTION__,ctx -> shared_var);/*在访问临界资源shared_var完成退出临界区时,申请调用pthread_mutex_unlock来释放锁,这样其他线程才能继续访问*/
pthread_mutex_unlock(&ctx -> lock);
/*延时,否则一个线程拿到锁之后会一直占有锁,另一个线程不能获得锁*/
sleep(1);
}printf("Thread worker1 exit...\n");
return NULL;
}
void *thread_worker2(void *args)
{
worker_ctx_t *ctx = (worker_ctx_t *)args;if( !args )
{
printf("%s() get invalid arguments\n",__FUNCTION__);
pthread_exit(NULL);
}printf("Thread worker2 [%ld] start running...\n",pthread_self());
while(1)
{
/*现在使用pthread_mutex_trylock来申请锁,这里使用的是非阻塞锁;如果锁现在被别的线程占用则返回非0值,如果没有被占用则返回0值*/
if( 0 != pthread_mutex_trylock(&ctx -> lock) )
{
continue;
}
printf("--- : %s before shared_var++ : %d\n",__FUNCTION__,ctx -> shared_var);
ctx->shared_var ++;
sleep(2);
printf("--- : %s after sleep shared_var : %d\n",__FUNCTION__,ctx -> shared_var);pthread_mutex_unlock(&ctx -> lock);
pthread_mutex_unlock(&ctx -> lock);
/*延时,否则一个线程拿到锁之后会一直占有锁,另一个线程不能获得锁*/
sleep(1);
}printf("Thread worker2 exit...\n");
return NULL;
}
运行结果如下:

现在我们发现用了互斥锁之后运行有序,是子线程worker2先运行在运行子线程worker1
二、死锁
1.死锁形成原因
如果多个线程要调用多个对象,则在上锁的时候可能会出现“死锁”。例如:A、B两个线程会同时使用到两个共享变量m和n,同时每个变量都有自己相应的锁M和N。这时A线程首先拿到M锁访问m,接下来他需要拿N锁来访问变量n;而如果B线程拿着N锁等待着M锁的话,就造成了线程死锁。

2.死锁产生的4个必要条件
(1)互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束;
(2)占有且等待:一个进程本身占有资源,同时还有资源未得到满足,正在等待其他进程释放该资源。(上述例子讲的)
(3)不可抢占:别人已经占有了某项资源,不能因为你要用这个资源就把他抢过来;
(4)循环等待:存在一个进程链,使得每个进程都占有下一个进程所需要的至少一种资源
当以上四个条件均满足,必然会造成死锁,发生死锁进程无法进行下去,他们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。
3.解决死锁的方式
(1)破坏占有且等待条件
方法一:所有的进程在开始之前,一次性的申请其在整个运行过程中所需要的全部资源。
<1>优点:实施简单且安全
<2>缺点:造成资源浪费,让进程发生饥饿现象。
方法二:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资源就开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的资源。这样提高了资源利用率,也减少进程饥饿问题。
(2)破坏“不可抢占条件”
当一个已经持有了一些资源的进程提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。这意味着进程已经占有的资源被短暂的抢占了。这种方法实现起来较为复杂,而且代价也比较大。释放已保持的资源很有可能会导致进程之前的工作实效等,反复的申请会导致进程的执行被无限的推迟,这样不仅会延长几次能的周转周期,还会影响系统的吞吐量。
(3)破坏“循环等待”条件
可以通过定义资源类型的线性顺序来预防,当一个进程占有编号为i的资源时,那么他下一次申请资源只能申请编号大于i的资源。
833

被折叠的 条评论
为什么被折叠?



