相关名词解析
同步:
同步就是指在一定时间内只允许一个线程访问某一资源。在该时间内其他线程不可访问该资源(实现方式:mutex互斥锁、condition variable条件变量、spin lock自旋锁(read/write lock读写锁是特殊自旋锁)、sem信号量等)
原子操作:
某段代码在执行时不会被其他线程影响,则构成了一个原子操作
临界区(非内核对象时,只在Win下多线程中存在,此处只提及不做过多说明):
临界区是一种轻量级机制,在某一时间内只允许一个线程执行某个给定代码段(在RTOS下表示禁止总中断和开启总中断之间的代码段)。临界区只能用于对同一进程内的线程进行同步
多线程同步实现方式(mutex、cond、sem、rw重要)
- 互斥锁的实现
主要实现函数:
1、pthread_mutex_t mutex_test = PTHREAD_MUTEX_INITIALIZER;//初始化静态定义的锁,一般用此即可
pthread_mutex_init(&mutex,NULL);//互斥锁被存放在参数mutex指向的内存中,第二个参数一般默认为NULL,使用缺省属性
2、pthread_mutex_lock(&mutex_test);//上锁
3、pthread_mutex_unlock(&mutex_test);//解锁
4、pthread_mutex_destroy (&mutex_test); //销毁互斥锁
/*************************************************************************
> File Name: mutexlock.c
> Author:lizhong
> Mail:
> Created Time: 2017年11月12日 星期日 06时11分54秒
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<sys/syscall.h>
#include<unistd.h>
pthread_mutex_t mutex_test = PTHREAD_MUTEX_INITIALIZER;
pid_t gettid(void)
{
return syscall(__NR_gettid);
}
void *Thread_Test(void *main_i)
{
int *tem_i = main_i;
while(1)
{
//创建互斥锁
pthread_mutex_lock(&mutex_test);
if(*tem_i < 10)
(*tem_i)++;
else
break;
printf("New ThreadID is:%lu, Main_i value is:%d\r\n", (unsigned long int)gettid(),*tem_i);
//解锁,从此可以看出,锁实现了一段代码的原子操作,而非只针对某变量的原子操作
pthread_mutex_unlock(&mutex_test);
sleep(1);
}
printf("New ThreadID is:%lu, Main_i value is:%d\r\n", (unsigned long int)gettid(),*tem_i);
pthread_mutex_unlock(&mutex_test);
}
int main(void)
{
int i = 0;
int j = 0;
pthread_t threadinfo[3];
for(j = 0; j < 3; ++j)
{
pthread_create(&threadinfo[j], NULL, Thread_Test, (void *)&i);
}
for(j = 0; j < 3; ++j)
{
pthread_join(threadinfo[j], NULL);
}
printf("All End\r\n");
}
- 条件变量的实现(注意:条件变量总是和一个互斥锁搭配使用,当一个线程的行为依赖于另外一个线程对共享数据状态的改变时,这时候就可以使用条件变量)
与互斥锁不同,条件变量是主动阻塞自身线程,直到某条件达成。但条件的检测需要在互斥锁的保护下进行。
主要实现函数:
1、pthread_cond_t temcond = PTHREAD_COND_INITIALIZER;//初始化静态定义的条件变量,一般用此即可
int pthread_cond_init(pthread_cond_t cv,const pthread_condattr_t cattr);//条件变量被存放在参数cv指向的内存中,cattr一般默认为NULL,使用缺省属性
2、int pthread_cond_signal(pthread_cond_t *cv);//通过条件变量cv发送消息,若多个线程等待,按入队顺序激活其中一个。调用后要立即释放本线程的互斥锁,因为在满足条件后pthread_cond_wait会在另一个线程立马上锁继续执行。pthread_cond_broadcast可以唤醒所有等待线程
3、int pthread_cond_wait(pthread_cond_t cv,
pthread_mutex_t mutex);//函数执行时先自动释放指定的锁,然后等待条件变量的变化。在函数调用返回之前,自动将指定的互斥量重新锁住。解锁->阻塞等待条件成立->上锁,继续执行。所以需要注意在另一线程条件变量成立signal后需要立即解锁
4、int pthread_cond_destroy(pthread_cond_t *cv);//销毁条件变量
/*************************************************************************
> File Name: ConditionVariable.c
> Author: lizhong
> Mail:
> Created Time: 2017年11月16日 星期四 05时26分09秒
************************************************************************/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/syscall.h>
pthread_mutex_t mutex_test = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_test = PTHREAD_COND_INITIALIZER;
void *One_Thread(void * tparam)
{
printf("One_Thread Start!\r\n");
pthread_mutex_lock(&mutex_test);
//解锁并等待条件变量信号,信号来后立即上锁继续执行该线程
pthread_cond_wait(&cond_test, &mutex_test);
printf("One_Thread Receive Signal\r\n");
pthread_mutex_unlock(&mutex_test);
}
void *Two_Thread(void * tparam)
{
unsigned char j = 0;
printf("Two_Thread Start!\r\n");
for(j = 0; j < 10; ++j)
{
if(j == 5)
{
printf("Two _Thread Ready Send Signal\r\n");
//发送信号的线程可以不使用互斥锁,根据需求来设计
pthread_cond_signal(&cond_test);
printf("Two_Thread Send Signal OK\r\n");
}
printf("Two_Thread j Value is:%d\r\n", j);
}
}
int main(void)
{
pthread_t one_thread;
pthread_t two_thread;
//可能会出现2号线程一直跑,
//1号线程却还没开始跑的情况,
//导致1号线程没能收到signal
pthread_create(&one_thread, NULL, One_Thread, (void *)0);
sleep(1);//确保1号线程先跑,也可使用SCHED_FIFO或SCHED_RR实时调度算法初始化线程
pthread_create(&two_thread, NULL, Two_Thread, (void *)0);
pthread_join(one_thread, NULL);
pthread_join(two_thread, NULL);
printf("ALL END\r\n");
}
- 信号量的实现(使用信号量,需要包含semaphore.h头文件):
POSIX信号量(linux中还有一种过时的System V信号量)分为有名信号量(值存在文件中)和无名信号量(值保持在内存中),都可以用于线程的同步(有名信号量还可用于进程间同步,无名信号量还可用于有亲缘关系的进程间同步)
无名信号量必须是共享变量,并且无名信号量要保护的变量也必须是共享变量。无名信号量主要实现函数(函数本身均属于原子操作,可以多线程同时执行):
1、int sem_init(sem_t *sem, int pshared, unsigned int value);//初始化一个信号量,value参数指定信号量的初始值,pshared参数指明信号量是由进程内线程共享(0值),还是进程间共享(非0值),value指定了信号量的初始值
2、int sem_wait(sem_t *sem);//若sem值非零则立刻执行减1操作,线程继续执行。若sem值为0则线程睡眠,等待直到其他线程操作sem使其非零时才会唤醒该线程,减1后继续执行(同条件变量一样,收到信号后随机继续跑一个线程,有可能是发信号的线程也有可能是收信号的线程)。若有两个线程都在等待同一个信号量变成非零值,则第三个线程增加1时,等待线程中只有一个随机线程能对信号量做减法并继续执行,另一个仍处于等待状态
3、int sem_post(sem_t *sem);//将信号量值加1
4、int sem_destroy(sem_t *sem);//该函数用于清理信号量,在清理信号量时若还有线程在等待该信号量则会报错
/*************************************************************************
> File Name: Sem.c
> Author:
> Mail:
> Created Time: 2017年11月19日 星期日 00时44分58秒
************************************************************************/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/syscall.h>
#include<semaphore.h>
sem_t sem_test;
void *One_Thread(void *tparam)
{
printf("One_Thread is Start!\r\n");
sem_wait(&sem_test);
printf("One_Thread received sem\r\n");
printf("One_Thread End!\r\n");
}
void *Two_Thread(void *tparam)
{
unsigned char i = 0;
printf("Two_Thread is Start!\r\n");
for(i = 0; i <= 5; ++i)
{
sleep(1);
printf("Two_Thread i value is:%d\r\n", i);
}
sem_post(&sem_test);
printf("Two_Thread End!\r\n");
}
int main(void)
{
pthread_t one_thread;
pthread_t two_thread;
sem_init(&sem_test, 0, 0);
pthread_create(&one_thread, NULL, One_Thread, (void *)0);
pthread_create(&two_thread, NULL, Two_Thread, (void *)0);
pthread_join(one_thread, NULL);
pthread_join(two_thread, NULL);
printf("All End\r\n");
}
- 读写锁的实现(适合一个线程写,多个线程读):
若一个线程用了读锁锁定临界区,则其他线程依然可以使用读锁来进入临界区(不加锁也可进入临界区)。若在此时一个线程想对临界区加写锁,则需要等待所有先前的读锁都解锁后才会锁上写锁(之后来的读上锁操作不会被写锁阻塞,依然可以上锁)(导致写锁饥饿的原因),也就是说,默认属性总是读锁优先的,若想改成写优先,则需要将attr属性设置为PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP(即写锁优先,看看源码便知道为什么了),这样一来,后来的写锁将不会被阻塞,之后的读锁,需等待写锁解锁后,才能拿到写锁(看看源码就明白了)。
若一个线程进行写锁加锁,则此后访问这个临界区的读锁或写锁都将进入阻塞。
主要实现函数(1-5重要):
1、pthread_rwlock_t rwlock_test = PTHREAD_RWLOCK_INITIALIZER;//静态初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t rwptr, const pthread_rwlockattr_t attr);//初始化读写锁,attr为空指针,表示使用缺省属性
2、int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);//读锁上锁
3、int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);//写锁上锁
4、int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);//解锁读写锁
5、int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);//销毁读写锁
6、int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);//尝试读锁上锁(非阻塞),成功返回0,失败返回错误码
7、int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);//尝试写锁上锁(非阻塞),成功返回0,失败返回错误码
/*************************************************************************
> File Name: RWlock.c
> Author:lizhong
> Mail:
> Created Time: 2017年11月19日 星期日 21时35分38秒
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/syscall.h>
#define MAXDATASIZE 1024
typedef struct
{
unsigned char flag_hasdata;//防止读者线程先运行时读数据
char data[MAXDATASIZE];
}Data_t;
pthread_rwlock_t rwlock_test = PTHREAD_RWLOCK_INITIALIZER;
//读者线程
void *One_Thread(void *tparam)
{
Data_t *pdata = tparam;
printf("One_Thread Start!\r\n");
while(1)
{
pthread_rwlock_rdlock(&rwlock_test);
if(pdata->flag_hasdata == 1)
{
printf("One_Thread has rev data:%s", pdata->data);//stdin中的数据自带换行
break;
}
pthread_rwlock_unlock(&rwlock_test);
}
printf("One_Thread End!\r\n");
}
//写者线程
void *Two_Thread(void *tparam)
{
Data_t *pdata = tparam;
printf("Two_Thread Start!,Please input data!\r\n");
while(1)
{
pthread_rwlock_wrlock(&rwlock_test);
pdata->flag_hasdata = 0;
fgets(pdata->data, MAXDATASIZE, stdin);
printf("Two_Thread has rev Data!\r\n");
pdata->flag_hasdata = 1;
pthread_rwlock_unlock(&rwlock_test);
break;
}
printf("Two_Thread End!\r\n");
}
int main(void)
{
pthread_t one_thread;
pthread_t two_thread;
Data_t tdata = {0};
pthread_create(&one_thread, NULL, One_Thread, (void *)&tdata);
sleep(1);
pthread_create(&two_thread, NULL, Two_Thread, (void *)&tdata);
pthread_join(one_thread, NULL);
pthread_join(two_thread, NULL);
printf("ALL END\r\n");
}
- 自旋锁的实现(在用户态暂时当做mutex看待,在用户态好像没有意义):
自旋锁与互斥锁相似,唯一的区别是当一个线程试图获取一个被锁定的互斥锁时,该操作会失败然后该线程会进入睡眠,让其他线程运行。而当一个线程试图获取一个自旋锁却没成功时,该线程会不断重试,直到最终成功为止,所以在内核态的自旋锁中单核CPU就算关中断也会导致死锁(用户态的单核多线程自旋锁,及其容易产生死锁,因为自旋会占用该CPU大量的时间片,导致主业务无法正常处理,从而难以解锁)。
主要实现函数(1-5重要):
1、pthread_spin_init(spinlock_t *lock, 0);//初始化自旋锁,将自旋锁设置为1(未上锁)
2、pthread_spin_lock(spinlock_t *lock);//循环等待,直到自旋锁解锁(被设置为1),然后再将自旋锁锁上(设置为0)
3、pthread_spin_unlock(spinlock_t *lock);//将自旋锁解锁(设置为1)
4、pthread_spin_lock_irq(spinlock_t *lock);//循环等待直到自旋锁解锁(被设置为1),然后将自旋锁锁上(设置为0),关中断
5、pthread_spin_unlock_irq(spinlock_t *lock);//将自旋锁解锁(设置为1),开中断
6、pthread_spin_is_locked(spinlock_t *lock);//如果自旋锁未上锁(值为1)则返回0,否则返回1
7、pthread_spin_unlock_wait();//等待,直到自旋锁解锁(被设置为1)
8、pthread_spin_trylock(spinlock_t *lock);//尝试锁上自旋锁(设置为0) ,如果原来为未上锁状态,则返回1,否则返回0
9、pthread_spin_lock_irqsave(spinlock_t *lock);//循环等待直到自旋锁解锁(被设置为1),随后将自旋锁上锁(设置为0)。关中断,将状态寄存器值存入flags
10、pthread_spin_unlock_irqsave(spinlock_t *lock);//解锁自旋锁(设置为1)。开中断,将状态寄存器值从flags存入状态寄存器
11、pthread_spin_lock_bh(spinlock_t *lock);//循环等待直到自旋锁解锁(被置为1),随后将自旋锁上锁(置为0)。阻止软中断底半部的执行
12、pthread_spin_unlock_bh(spinlock_t *lock);//将自旋锁解锁(设置为1)。开启底半部的执行
*************************************************************************
> File Name: Spinlock.c
> Author:lizhong
> Mail:
> Created Time: 2017年11月18日 星期六 02时44分07秒
************************************************************************/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/syscall.h>
pthread_spinlock_t spinlock_test;
void *One_Thread(void *tparam)
{
unsigned char i = 0;
printf("One_Thread Start!\r\n");
pthread_spin_lock(&spinlock_test);
for(i = 0; i <= 5; ++i)
{
sleep(1);
printf("One_Thread i value is:%d\r\n", i);
}
pthread_spin_unlock(&spinlock_test);
}
void *Two_Thread(void *tparam)
{
unsigned char i = 0;
printf("Two_Thread Start!\r\n");
//pthread_spin_lock(&spinlock_test);
for(i = 0; i <= 5; ++i)
{
sleep(1);
//两线程依然同时输出,说明依然存在内核抢占,线程调度
printf("Two_Thread i value is:%d\r\n", i);
}
//pthread_spin_unlock(&spinlock_test);
}
int main(void)
{
pthread_t one_thread;
pthread_t two_thread;
pthread_spin_init(&spinlock_test, 0);
pthread_create(&one_thread, NULL, One_Thread, (void *)0);
pthread_create(&two_thread, NULL, Two_Thread, (void *)0);
pthread_join(one_thread, NULL);
pthread_join(two_thread, NULL);
printf("ALL END\r\n");
}
补充说明:
在linux中大部分用户态API都是使用glibc库实现的,在我这台Centos7(内核版本为3.10.0)上对应的glibc版本是2.17(可通过rpm -qa | grep glibc命令进行查看),通过以下路径可以拿到源码
http://git.savannah.gnu.org/cgit/hurd/glibc.git/refs/tags
现在,对API的使用或实现,哪里有疑问的直接戳源码查看就好了,这样效率最高了