线程同步
互斥锁(不占内存)
互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。在同一时刻它通常只允许一个线程执行一个关键部分的代码。
初始化:
初始化互斥锁的两种方式:
//静态赋值法
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIAER;
//通过pthread_mutex_init函数初始化
int pthread_mutex_init(pthread_mutex_t *mutex,
constt pthread_mutexattr_t *mutexatter);
mutexatter表示互斥锁的属性,NULL则使用默认属性
互斥锁的属性
* 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_t *mutex);//阻塞申请互斥锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞申请互斥锁
pthread_mutex_lock()加锁时,如果mutex已被锁住,当前尝试加锁的线程就会阻塞,直到互斥锁被其它线程释放。当pthread_mutex_lock()函数返回时,说明互斥锁已经被当前进程成功加锁。
pthread_mutex_trylock()加锁时,如果mutex已被锁住,它将立即返回,返回错误代码EBUSY,而不是阻塞等待
attention:不论哪种类型的锁,都不可能被两个线程同时得到,其中一个必须等待解锁。在同一进程的线程,如果加锁后没有解锁,则其他进程无法再获得该锁。
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
解锁要满足的条件:
1、互斥锁要处于加锁状态
2、调用该函数的线程必须是给互斥锁加锁的线程
(解锁后如果有其它进程正在等待互斥锁,等待队列的第一个线程会获得该互斥锁)
清除
int pthread_mutex_destroy(pthread_mutex_t *mutex);
释放锁的资源,清除锁要求锁当前处于开放状态。若锁处于锁定状态,函数返回EBUSY,该函数执行成功返回0。
互斥锁实例:打印机
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex; //互斥锁
// 打印机,一段时间内只能被一个用户使用,不能被多人同时使用
void printer(char *str)
{
pthread_mutex_lock(&mutex); //上锁
while(*str!='\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
pthread_mutex_unlock(&mutex); //解锁
}
void printer2(char *str)
{
//pthread_mutex_lock(&mutex); //上锁
while(*str!='\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
//pthread_mutex_unlock(&mutex); //解锁
}
// 线程一
void *thread_fun_1(void *arg)
{
char *str = "hello";
printer(str); //打印
}
// 线程二
void *thread_fun_2(void *arg)
{
char *str = "world";
printer(str);
//printer2(str); //打印
}
int main(void)
{
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL); //初始化互斥锁
// 创建 2 个线程
pthread_create(&tid1, NULL, thread_fun_1, NULL);
pthread_create(&tid2, NULL, thread_fun_2, NULL);
// 等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex); //销毁互斥锁
return 0;
}
/*
* 上锁
[limeng@KID 8.1]$ gcc huchisuo.c -lpthread
[limeng@KID 8.1]$ ./a.out
hello
world
*不上锁
[limeng@KID 8.1]$ gcc huchisuo.c -lpthread
[limeng@KID 8.1]$ ./a.out
hweolrlldi
thread1上锁,thread2不上锁
[limeng@KID 8.1]$ ./a.out
hweolrllod
*/
//可见使用互斥锁就是为了让某一资源在一段时间内一个进程被独占,从而不混乱,但是一个线程对某一资源上锁,其它没有上锁的资源还可以对其操作。所以,互斥锁又叫建议锁,或者协同锁。但不是强制性的。
条件变量
1.一个等待使用资源的线程等待“条件变量被设置为真”
2.另一个线程在使用完资源后“设置条件为真”
初始化
//静态赋值法
pthread_cond_t cond=PTHREAD_COND_INITIALIER;
//使用init函数
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
cond_attr参数是条件变量的属性,由于其并没有得到实现,所以它的值通常为NULL
等待条件成立
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
pthread_cond_wait函数释放由mutex指向的互斥锁,同时当前进程关于cond指向的条件变量阻塞,直到条件信号被唤醒。
pthread_cond_timewait将阻塞直到条件变量获得信号或者经由abstime指定的时间。如果在指定时间结束都没有条件满足,则返回ETIMEOUT,结束等待。
解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal激活一个等待条件成立的线程,存在多个等待线程时,安入队顺序激活
pthread_cond_broadcast激活所有等待条件的线程
清除条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
只有在没有线程等待该条件变量的时候才能清除这个条件变量,否则返回EBUSY
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void * thread1(void * arg)
{
pthread_cleanup_push((void *)pthread_mutex_unlock,&mutex);
while(1){
printf("thread1 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
printf("thread1 applied the condition\n");
pthread_mutex_unlock(&mutex);
printf("unlock\n");
printf("\n");
sleep(1);
}
pthread_cleanup_pop(0);
}
void *thread2(void * arg)
{
while(1){
printf("thread2 is running\n");
pthread_mutex_lock(&mutex);
printf("thread2 get\n");
pthread_cond_wait(&cond,&mutex);
printf("thread2 applied the condition\n");
pthread_mutex_unlock(&mutex);
printf("unlock\n");
printf("\n");
sleep(1);
}
}
int main(void)
{
pthread_t tid1,tid2;
printf("condition variable study!\n");
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_create(&tid1,NULL,(void *)thread1,NULL);
pthread_create(&tid2,NULL,(void *)thread2,NULL);
do{
sleep(2);
pthread_cond_signal(&cond);
}while(1);
sleep(50);
pthread_exit(0);
}
/*
pthread_cleanup_push((void *)pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
// do some work
pthread_mutex_unlock(&mut);
pthread_cleanup_pop(0);
本来do some work之后是有pthread_mutex_unlock(&mut);这句,也就是有解锁操作,但是在do some work时会出现非正常终止,那样的话,系统会根据pthread_cleanup_push中提供的函数,和参数进行解锁操作或者其他操作,以免造成死锁!
*/
/*在这里是thread1先上锁,然后通过pthread_cond_wait使其阻塞,进入等待队列,释放锁;这时thread2开始上锁,接着也因为条件变量阻塞并释放锁,进入等待队列。这时主线程通过pthread_cond_signal激活一个等待条件成立的线程,这时thread1在等待队列的前面,所以thread1被激活,这时互斥锁重新被锁上,然后进行完之后,解锁。然后解锁,上锁,条件变量阻塞再次进入等待队列,接下来该激活thread2,以此类推(sleep先不考虑,大致就是这样)*/
异步信号
信号与任何线程通信都是异步的。信号到达的时间是不定的。如果有多个线程接受信号,只有一个被选中。如果并发的多个信号被送到一个进程,每一个将被不同的线程处理。如果所有线程都屏蔽该信号,则这些信号被挂起,直到有信号解除屏蔽来处理它们。
//向特定的线程发送信号signo
int pthread_kill(pthread_t threadid,int signo);
//设置线程的信号屏蔽码
int pthread_sigmask(int how,const sigset_t *newmask,sigset_t *oldmask);
//阻塞线程
int sigwait(const sigset_t *set,int *sig);
关于信号,以后到信号那一章再仔细研究。