网站建设文化价格,wordpress 学校模版,徐州手机网站建设制作,长沙知名网站建设目录 多线程编临界资源访问
互斥锁 API 简述
初始化互斥量
互斥量加锁/解锁
互斥量加锁(非阻塞方式)
互斥量销毁
程序示例
多线程编执行顺序控制
信号量 API 简述
初始化信号量
信号量 P/V 操作
信号量申请(非阻塞方式)
信号量销毁
程序示例
条件变量
创建和销毁…目录 多线程编临界资源访问
互斥锁 API 简述
初始化互斥量
互斥量加锁/解锁
互斥量加锁(非阻塞方式)
互斥量销毁
程序示例
多线程编执行顺序控制
信号量 API 简述
初始化信号量
信号量 P/V 操作
信号量申请(非阻塞方式)
信号量销毁
程序示例
条件变量
创建和销毁条件变量
等待条件变量
通知条件变量
程序示例 总结
线程使用流程图
互斥量使用流程图
信号量使用流程图 多线程编临界资源访问
当线程在运行过程中去操作公共资源如全局变量的时候可能会发生彼 此“矛盾”现象。
例如线程 1 企图想让变量自增而线程 2 企图想要变量自减 两个线程存在互相竞争的关系导致变量永远处于一个“平衡状态”两个线程互相竞争线程 1 得到执行权后将变量自加当线程 2 得到执行权后将变量自减 变量似乎永远在某个范围内浮动无法到达期望数值
如例程 9 所示
测试例程 9Phtread_txex9.c
#define _GNU_SOURCE
#include pthread.h
#include stdio.h
#include unistd.h
#include errno.hint Num 0;void *fun1(void *arg)
{while(Num 3){Num;printf(%s:Num %d\n,__FUNCTION__,Num);sleep(1);}pthread_exit(NULL);
}void *fun2(void *arg)
{while(Num -3){Num--;printf(%s:Num %d\n,__FUNCTION__,Num);sleep(1);}pthread_exit(NULL);
}int main()
{int ret;pthread_t tid1,tid2;ret pthread_create(tid1,NULL,fun1,NULL);if(ret ! 0){perror(pthread_create);return -1;}ret pthread_create(tid2,NULL,fun2,NULL);if(ret ! 0){perror(pthread_create);return -1;}pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;
}运行结果
为了解决上述对临界资源的竞争问题pthread 线程引出了互斥锁来解决临界资源访问。通过对临界资源加锁来保护资源只被单个线程操作待操作结束后解锁其余线程才可获得操作权。
互斥锁 API 简述
多个线程都要访问某个临界资源比如某个全局变量时需要互斥地访问 我访问时你不能访问。
可以使用以下函数进行互斥操作。
初始化互斥量
函数原型如下 int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr); 该函数初始化一个互斥量第一个参数是改互斥量指针第二个参数为控制互斥量的属性一般为 NULL。当函数成功后会返回 0代表初始化互斥量成功。
当然初始化互斥量也可以调用宏来快速初始化代码如下 pthread_mutex_t mutex PTHREAD_MUTEX_INITALIZER; 互斥量加锁/解锁
函数原型如下 互斥量加锁阻塞/解锁 #include pthread.h int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 成功返回 0 lock 函数与 unlock 函数分别为加锁解锁函数只需要传入已经初始化好的 pthread_mutex_t 互斥量指针。成功后会返回 0。
当某一个线程获得了执行权后执行 lock 函数一旦加锁成功后其余线 程遇到 lock 函数时候会发生阻塞直至获取资源的线程执行 unlock 函数后。 unlock 函数会唤醒其他正在等待互斥量的线程。
特别注意的是当获取 lock 之后必须在逻辑处理结束后执行 unlock否则会发生死锁现象导致其余线程一直处于阻塞状态无法执行下去。在使用互 斥量的时候尤其要注意使用 pthread_cancel 函数防止发生死锁现象
互斥量加锁(非阻塞方式)
函数原型如下: 互斥量加锁非阻塞 #include pthread.h int pthread_mutex_trylock(pthread_mutex_t *mutex); 该函数同样也是一个线程加锁函数但该函数是非阻塞模式通过返回值来 判断是否加锁成功用法与上述阻塞加锁函数一致。
互斥量销毁
函数原型如下 互斥量销毁 #include pthread.h int pthread_mutex_destory(pthread_mutex_t *mutex); 成功返回 0 该函数是用于销毁互斥量的传入互斥量的指针就可以完成互斥量的销毁成功返回 0。
程序示例
测试例程 10Phtread_txex10.c
#define _GNU_SOURCE
#include pthread.h
#include stdio.h
#include unistd.h
#include errno.hpthread_mutex_t mutex; //互斥量变量 一般申请全局变量int Num 0; //公共临界变量void *fun1(void *arg)
{pthread_mutex_lock(mutex); //加锁 若有线程获得锁则会阻塞while(Num 3){Num;printf(%s:Num %d\n,__FUNCTION__,Num);sleep(1);}pthread_mutex_unlock(mutex); //解锁pthread_exit(NULL); //线程退出 pthread_join 会回收资源
}void *fun2(void *arg)
{ pthread_mutex_lock(mutex); //加锁 若有线程获得锁则会阻塞while(Num -3){Num--;printf(%s:Num %d\n,__FUNCTION__,Num);sleep(1);}pthread_mutex_unlock(mutex); //解锁pthread_exit(NULL); //线程退出 pthread_join 会回收资源
}int main()
{int ret;pthread_t tid1,tid2; ret pthread_mutex_init(mutex,NULL); //初始化互斥量if(ret ! 0){perror(pthread_mutex_init);return -1;}ret pthread_create(tid1,NULL,fun1,NULL); //创建线程 1if(ret ! 0){perror(pthread_create);return -1;}ret pthread_create(tid2,NULL,fun2,NULL); //创建线程 2if(ret ! 0){perror(pthread_create);return -1;}pthread_join(tid1,NULL); //阻塞回收线程 1pthread_join(tid2,NULL); //阻塞回收线程 2pthread_mutex_destroy(mutex); //销毁互斥量return 0;
}运行结果
上述例程通过加入互斥量保证了临界变量某一时刻只被某一线程控制 实现了临界资源的控制。需要说明的是线程加锁在循环内与循环外的情况。
本历程在进入 while 循环前进行了加锁操作在循环结束后进行的解锁操作 如果将加锁解锁全部放入 while 循环内作为单核的机器执行结果无异当有多核机器执行代码时可能会发生“抢锁”现象这取决于操作系统底层的实现。
多线程编执行顺序控制
解决了临界资源的访问但似乎对线程的执行顺序无法得到控制因线程都是无序执行之前采用 sleep 强行延时的方法勉强可以控制执行顺序但此方法在实际项目情况往往是不可取的其仅仅可解决线程创建的顺序当创建之后执行的顺序又不会受到控制于是便引入了信号量的概念解决线程执行顺序。
测试例程 11Phtread_txex11.c
#define _GNU_SOURCE
#include pthread.h
#include stdio.h
#include unistd.h
#include errno.hvoid *fun1(void *arg)
{printf(%s:Pthread Come!\n,__FUNCTION__);pthread_exit(NULL);
}void *fun2(void *arg)
{printf(%s:Pthread Come!\n,__FUNCTION__);pthread_exit(NULL);
}void *fun3(void *arg)
{printf(%s:Pthread Come!\n,__FUNCTION__);pthread_exit(NULL);
}int main()
{int ret;pthread_t tid1,tid2,tid3;ret pthread_create(tid1,NULL,fun1,NULL);if(ret ! 0){perror(pthread_create);return -1;}ret pthread_create(tid2,NULL,fun2,NULL);if(ret ! 0){perror(pthread_create);return -1;}ret pthread_create(tid3,NULL,fun3,NULL);if(ret ! 0){perror(pthread_create);return -1;}pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_join(tid3,NULL);return 0;
}运行结果通过上述例程可以发现多次执行该函数其次序是无序的线程之间的竞 争无法控制通过使用信号量来使得线程顺序为可控的。
信号量 API 简述
注意信号量跟互斥量不一样互斥量用来防止多个线程同时访问某个临界资源。信号量起通知作用线程 A 在等待某件事线程 B 完成了这件事后就 可以给线程 A 发信号。
初始化信号量
函数原型如下 int sem_init(sem_t *sem,int pshared,unsigned int value); 该函数可以初始化一个信号量第一个参数传入 sem_t 类型指针第二个参数传入 0 代表线程控制否则为进程控制第三个参数表示信号量的初始值0 代表阻塞1 代表运行。待初始化结束信号量后若执行成功会返回 0。
信号量 P/V 操作
函数原型如下 #include pthread.h int sem_wait(sem_t *sem); int sem_post(sem_t *sem); 成功返回 0 sem_wait 函数作用为检测指定信号量是否有资源可用若无资源可用会阻塞等待若有资源可用会自动的执行“sem-1”的操作。所谓的“sem-1”是与上述 初始化函数中第三个参数值一致成功执行会返回 0。sem_post 函数会释放指定信号量的资源执行“sem1”操作。
通过以上 2 个函数可以完成所谓的 PV 操作即信号量的申请与释放完成 对线程执行顺序的控制。
信号量申请(非阻塞方式)
函数原型如下 #include pthread.h int sem_trywait(sem_t *sem); 成功返回 0 此函数是信号量申请资源的非阻塞函数功能与 sem_wait 一致唯一区别在于此函数为非阻塞。
信号量销毁
函数原型如下 #include pthread.h int sem_destory(sem_t *sem); 成功返回 0 该函数为信号量销毁函数执行过后可将信号量进行销毁
程序示例
测试例程 12Phtread_txex12.c
#define _GNU_SOURCE
#include pthread.h
#include stdio.h
#include unistd.h
#include errno.h
#include semaphore.hsem_t sem1,sem2,sem3;//申请的三个信号量变量void *fun1(void *arg)
{sem_wait(sem1);//因sem1本身有资源所以不被阻塞 获取后sem1-1 下次会会阻塞printf(%s:Pthread Come!\n,__FUNCTION__);sem_post(sem2);// 使得sem2获取到资源pthread_exit(NULL);
}void *fun2(void *arg)
{sem_wait(sem2);//因sem2在初始化时无资源会被阻塞直至14行代码执行 不被阻塞 sem2-1 下次会阻塞printf(%s:Pthread Come!\n,__FUNCTION__);sem_post(sem3);// 使得sem3获取到资源pthread_exit(NULL);
}void *fun3(void *arg)
{sem_wait(sem3);//因sem3在初始化时无资源会被阻塞直至22行代码执行 不被阻塞 sem3-1 下次会阻塞printf(%s:Pthread Come!\n,__FUNCTION__);sem_post(sem1);// 使得sem1获取到资源pthread_exit(NULL);
}int main()
{int ret;pthread_t tid1,tid2,tid3;ret sem_init(sem1,0,1); //初始化信号量1 并且赋予其资源if(ret 0){perror(sem_init);return -1;}ret sem_init(sem2,0,0); //初始化信号量2 让其阻塞if(ret 0){perror(sem_init);return -1;}ret sem_init(sem3,0,0); //初始化信号3 让其阻塞if(ret 0){perror(sem_init);return -1;}ret pthread_create(tid1,NULL,fun1,NULL);//创建线程1if(ret ! 0){perror(pthread_create);return -1;}ret pthread_create(tid2,NULL,fun2,NULL);//创建线程2if(ret ! 0){perror(pthread_create);return -1;}ret pthread_create(tid3,NULL,fun3,NULL);//创建线程3if(ret ! 0){perror(pthread_create);return -1;}/*回收线程资源*/pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_join(tid3,NULL);/*销毁信号量*/sem_destroy(sem1);sem_destroy(sem2);sem_destroy(sem3);return 0;
}运行结果
该例程加入了信号量使得线程的执行顺序变为可控的。在初始化信号量时 将信号量 1 填入资源第一个线程调用 sem_wait 函数可以成功获得信号量在 执行完逻辑后使用 sem_pos 函数来释放。当执行函数 sem_wait 后会执行 sem 自减操作使下一次竞争被阻塞直至通过 sem_pos 被释放
上述例程因 38 行初始化信号量 1 时候使其默认获取到资源
第 43、48 行 初始化信号量 2、3 时候使之没有资源。于是在线程处理函数中每个线程通过 sem_wait 函数来等待资源发生阻塞。因信号量 1 初始值为有资源故可以 先执行线程 1 的逻辑。待执行完第 12 行 sem_wait 函数会导致 sem1-1使得 下一次此线程会被阻塞。继而执行至 14 行通过 sem_post 函数使 sem2 信号量 获取资源从而冲破阻塞执行线程 2 的逻辑...以此类推完成线程的有序控制。
条件变量
条件变量时一种同步机制用来通知其他线程条件满足了。一般是用来通知对方共享数据的状态信息因此条件变量是结合互斥量来使用的。
创建和销毁条件变量
函数原型如下 #include pthread.h // 初始化条件变量 pthread_cond_t cond PTHREAD_COND_INITIALIZER; int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);//cond_attr 通常为 NULL // 销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond); 这些函数成功时都返回 0 等待条件变量
函数原型如下 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 这需要结合互斥量一起使用示例代码如下 pthread_mutex_lock(g_tMutex); // 如果条件不满足则会 unlock g_tMutex // 条件满足后被唤醒会 lock g_tMutex pthread_cond_wait(g_tConVar, g_tMutex); /* 操作临界资源 */ pthread_mutex_unlock(g_tMutex); 通知条件变量
函数原型如下 int pthread_cond_signal(pthread_cond_t *cond); pthread_cond_signal 函数只会唤醒一个等待 cond 条件变量的线程示例代码如下 pthread_cond_signal(g_tConVar); 程序示例 总结
线程使用流程图
有关多线程的创建流程如图 所示首先需要创建线程一旦线程创 建完成后线程与线程之间会发生竞争执行抢占时间片来执行线程逻辑。在 创建线程时候可以通过创建线程的第四个参数传入参数在线程退出时亦可 传出参数被线程回收函数所回收获取到传出的参数。
互斥量使用流程图
当多个线程出现后会遇到同时操作临界公共资源的问题当线程操作公 共资源时需要对线程进行保护加锁防止其与线程在此线程更改变量时同时更 改变量待逻辑执行完毕后再次解锁使其余线程再度开始竞争。互斥锁创建 流程下图所示。 信号量使用流程图
当多个线程出现后同时会遇到无序执行的问题。有时候需要对线程的执行顺序做出限定变引入了信号量通过 PV 操作来控制线程的执行顺序如下图所示