Linux-线程同步
线程同步一、概述线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。二、互斥锁(mutex)2.1 - 互斥锁常用函数在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。...
线程同步
线程同步
一、概述
- 线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。
- linux下提供了多种方式来处理线程同步,最常用的是
互斥锁
、条件变量
和信号量
。
二、互斥锁(mutex)
2.1 - 互斥锁常用函数
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
-
初始化锁
。在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行初始化。可以用静态和动态两种j方式初始化:-
静态分配:POSIX定义了一个宏来静态初始化互斥锁,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
动态分配 :采用 pthread_mutex_init() 函数来初始化互斥锁,中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);
-
-
互斥锁属性
:互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当(glibc2.2.3,linuxthreads0.9)有四个值可供选择:- PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
-
加锁
。不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。 -
普通加锁:对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。
int pthread_mutex_lock(pthread_mutex *mutex);
- 测试加锁:pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
解锁
。在完成了对共享资源的访问后,要对互斥量进行解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
销毁锁
。锁在是使用完成后,需要进行销毁以释放资源。
int pthread_mutex_destroy(pthread_mutex *mutex);
2.2 - 互斥锁应用示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//定义累加次数
#define NLOOP 5000
//定义累加的全局变量
int counter;
//静态分配 定义是一个默认锁
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
//两个线程执行的函数
void *doit(void *);
int main(int argc, char **argv) {
//创建两个线程
pthread_t tidA, tidB;
pthread_create(&tidA, NULL, doit, NULL);
pthread_create(&tidB, NULL, doit, NULL);
//等待两个线程结束
pthread_join(tidA, NULL);
pthread_join(tidB, NULL);
return 0;
}
void *doit(void *vptr) {
int i, val;
//累加NLOOP次
for (i = 0; i < NLOOP; i++) {
//加锁,
pthread_mutex_lock(&counter_mutex);
val = counter;
printf("%x: %d\n", (unsigned int)pthread_self(), val + 1);
counter = val + 1;
//解锁
pthread_mutex_unlock(&counter_mutex);
}
return NULL;
}
运行结果
三、条件变量(cond)
条件变量是利用线程间共享全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。
初始化条件变量
条件变量和互斥锁一样,都有静态和动态两种创建方式。
- 静态方式使用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中没有实现,因此cond_attr值通常为NULL,且被忽略。
-
有两个等待函数
(1)无条件等待
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
(2)计时等待
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
-
如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
-
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求(用 pthread_cond_wait() 或 pthread_cond_timedwait() 请求 竞争条件 。mutex互斥锁必须是普通锁 或者适应锁
-
且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
-
-
激发条件
(1)激活一个等待该条件的线程(存在多个等待线程时按入队顺序激活其中一个)
int pthread_cond_signal(pthread_cond_t *cond);
(2)激活所有等待线程
int pthread_cond_broadcast(pthread_cond_t *cond);
-
销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
只有在没有线程在该条件变量上等待的时候才能销毁这个条件变量,否则返回EBUSY
3.1 - 生产消费者模型(条件变量与互斥量)
生产者与消费应用示例(条件变量与互斥量):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//生产者消费者模型
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//互斥锁
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;//条件变量
struct msg{
struct msg* next;
int num;
};
struct msg *head;
void *customer(void *p)//消费者
{
//假如消费者先执行
struct msg *mp;
for( ; ; )
{
pthread_mutex_lock(&lock);
while(head == NULL)//没有食物,则阻塞等待
pthread_cond_wait(&has_product,&lock);
//删除这个节点(消费掉食物)
mp = head;
head = mp->next;
pthread_mutex_unlock(&lock);
//消费掉
printf("customer %d\n",mp->num);
free(mp);
sleep(1);
}
}
void *product(void *p)//生产者
{
struct msg *mp;
for( ; ; )
{
mp = malloc(sizeof(struct msg));
//模拟一张饼
mp->num = rand()%1000+1;
printf("product %d\n",mp->num);
//向链表加入节点
pthread_mutex_lock(&lock);
//头插法
mp->next = head;
head = mp;
//唤醒阻塞在条件上的进程
pthread_cond_signal(&has_product);
pthread_mutex_unlock(&lock);
sleep(1);
}
}
int main()
{
pthread_t pid ,cid;
pthread_create(&pid, NULL, &customer, NULL);
pthread_create(&cid, NULL, &product, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
运行结果
四、 信号量
如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。
线程使用的基本信号量函数有四个:
头文件:#include <semaphore.h>
-
初始化信号量
int sem_init (sem_t *sem , int pshared, unsigned int value);
参数:
- sem - 指定要初始化的信号量;
- pshared - 信号量 sem 的共享选项,linux只支持0,表示它是当前进程的局部信号量;
- value - 信号量 sem 的初始值。
-
信号量值加1
给参数sem指定的信号量值加1。
int sem_post(sem_t *sem);
-
信号量值减1
给参数sem指定的信号量值减1。
int sem_wait(sem_t *sem);
如果sem所指的信号量的数值为0,函数将会等待直到有其它线程使它不再是0为止。 -
销毁信号量
毁指定的信号量。
int sem_destroy(sem_t *sem);
4.1 - 生产消费者模型(信号量)
生产者与消费应用示例(信号量):
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
//定义最大产品数 5
#define NUM 5
//存放产品的数组
int queue[NUM];
//定义空格数 产品数
sem_t blank_number, product_number;
//生产者操作
void *producer(void *arg) {
int p = 0;
while (1) {
//空格数-1
sem_wait(&blank_number);
//给产品随机赋一个值
queue[p] = rand() % 1000 + 1;
printf("Produce %d\n", queue[p]);
//产品数+1
sem_post(&product_number);
//数组到底的时候重新回到开头
p = (p+1)%NUM;
sleep(rand()%5);
}
}
//消费者操作
void *consumer(void *arg) {
int c = 0;
while (1) {
//产品数-1
sem_wait(&product_number);
printf("Consume %d\n", queue[c]);
queue[c] = 0;
//空格数+1
sem_post(&blank_number);
c = (c+1)%NUM;
sleep(rand()%5);
}
}
int main(int argc, char *argv[]) {
pthread_t pid, cid;
//初始化时,空格数为NUM,产品为0个
sem_init(&blank_number, 0, NUM);
sem_init(&product_number, 0, 0);
//创建两个进程
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
//等待两个进程结束
pthread_join(pid, NULL);
pthread_join(cid, NULL);
//销毁两个进程,并释放资源
sem_destroy(&blank_number);
sem_destroy(&product_number);
return 0;
}
应用示例:
参考来源:
https://blog.csdn.net/liu0808/article/details/80509255
https://baike.baidu.com/item/互斥锁/841823?fr=aladdin
更多推荐
所有评论(0)