一、互斥锁mutex
锁的概念
对一块临界资源加锁,临界资源在使用中只能被一个线程使用,例如厕所的坑位,只能一个人去蹲
//创建10个线程
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#define THREAD_COUNT 10
void *func(void *args)
{
int *pcount = (int*)args;
int i=0;
while(i++<100000){
(*pcount)++;//10个线程共用的资源
usleep(1);
}
}
int main(){
pthread_t thid[10]={0};
int count = 0;
for (int i = 0;i < THREAD_COUNT; i++) {
pthread_create(&thid[i], null, func, &count);
/*第一个参数是线程的id,第二个是线程的属性,第三个是入口函数,第四个是传到子线程的参数*/
}
for(int i=0; i < 100; i++){//每隔一秒打印一次,最后理想结果是100万
printf("count -->%d\n",count);
sleep(1);
}
return 0;
}
我们来运行一下,十个线程共有count这个资源同时加一的过程,发觉最终的值小于100万,为什么会出现这种事呢
我们来看看自加一的操作在汇编当中的实现,先在内存中把idx move到eax寄存器里边,再通过寄存器进行一个自增,再把idx写回到原来的内存中
问题出现在哪呢?比如说在下面这张图里最开始idx=20,先是线程1切换时保存了寄存器的状态,然后从线程2切换回来时,线程1里边的值还是20,线程1再执行+1操作,因此在线程1和2中都执行了+1,但是效果只是执行了一次+1,所以总共执行100万次的+1会导致最后的值小于100万
我们该怎么改呢,我们试试加锁,只允许一个线程运行
我们用互斥锁
//创建10个线程
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#define THREAD_COUNT 10
pthread_mutex_t mutex;
void *func(void *args)
{
int *pcount = (int*)args;
int i=0;
//外边加锁的话,其中usleep是没有必要,一定要对需要加锁的临界资源加锁,非临界资源就不用加了
while(i++<100000){
pthread_mutex_lock(&mutex);
(*pcount)++;
pthread_mutex_unlock(&mutex);
usleep(1);
}
}
int main(){
pthread_t thid[10]={0};
int count = 0;
pthread_mutex_init(&mutex, NULL);
for (int i = 0;i < THREAD_COUNT; i++) {
pthread_create(&thid[i], NULL, func, &count);
}
for(int i=0; i < 100; i++){//每隔一秒打印一次,最后理想结果是100万
printf("count -->%d\n",count);
sleep(1);
}
return 0;
}
实现效果
二、自旋锁
我们来看看自旋锁,自旋锁的使用和互斥锁是一样的
//创建10个线程
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#define THREAD_COUNT 10
//pthread_mutex_t mutex;
pthread_spinlock_t spinlock;
void *func(void *args)
{
int *pcount = (int*)args;
int i=0;
//外边加锁的话,其中usleep是没有必要,一定要对需要加锁的临界资源加锁,非临界资源就不用加了
while(i++<100000){
//pthread_mutex_lock(&mutex);
pthread_spin_lock(&spinlock);
(*pcount)++;
//pthread_mutex_unlock(&mutex);
pthread_spin_unlock(&spinlock);
usleep(1);
}
}
int main(){
pthread_t thid[10]={0};
int count = 0;
pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED);
for (int i = 0;i < THREAD_COUNT; i++) {
pthread_create(&thid[i], NULL, func, &count);
}
for(int i=0; i < 100; i++){//每隔一秒打印一次,最后理想结果是100万
printf("count -->%d\n",count);
sleep(1);
}
return 0;
}
区别在哪呢
mutex互斥锁当遇到锁被占用时,会引起线程切换,使用场景在于操作复杂,有系统调用
spinlock自旋锁当遇到锁被占用时会一直在锁这里停留,直到锁被让出,,使用场景在于临界资源操作简单,没有系统调用
如何去判断复杂和简单呢,这里的复杂指的是比线程切换还重的复杂
三.原子操作
cpu执行的最小单元是指令,那我们有没有办法把这三条指令变成一条指令执行呢,这里我们引入原子操作。
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#define THREAD_COUNT 10
//pthread_mutex_t mutex;
pthread_spinlock_t spinlock;
int inc(int *value, int add) {
int old;
__asm__ volatile(
"lock; xaddl %2, %1;"|
:"=a" (old)
:"m" (*value), "a" (add)
: "cc", "memory"
);
return old;
}
void *func(void *args)
{
int *pcount = (int*)args;
int i=0;
while(i++<100000){
//(*pcount)++;
inc(pcount,1);
usleep(1);
}
}
int main(){
pthread_t thid[10]={0};
int count = 0;
pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED);
for (int i = 0;i < THREAD_COUNT; i++) {
pthread_create(&thid[i], NULL, func, &count);
}
for(int i=0; i < 100; i++){//每隔一秒打印一次,最后理想结果是100万
printf("count -->%d\n",count);
sleep(1);
}
return 0;
}
原子操作核心需要cpu指令集支持才可以,原子操作只能在操作比较简单的情况下运用
CAS是原子操作的一种,扩写为compare and swap,
例如:cmppxchg(a,b,c);—>if(a==b) a=c;这就是CAS的一种
四、线程私有空间
解决函数内部调用时,在线程当中使用次数较多,且不能当作函数的参数传进去时,我们就需要线程的私有空间
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#define THREAD_COUNT 3
pthread_key_t key;
typedef void *(*thread_cb)(void*);
void print_tread1_key(void) {
int *p = (int*)pthread_getspecific(key);//我们利用的key来获取这个值
printf("thread 1:%d\n", *p);
}
void print_tread2_key(void) {
int *ptr = (char*)pthread_getspecific(key);
printf("thread 2:%d\n", ptr);
}
void print_tread3_key(void) {
struct pair *p =(struct pair*)pthread_getspecific(key);
printf("thread 2:%d, %d\n", p->x, p->y);
}
void *thread1_proc(void *args) {
int i=5;
pthread_setspecific(key, &i);//私有空间设在key这里
print_tread1_key();//我们并没有把key当做参数传进去,我们线程的私有空间就是key
}
void *thread2_proc(void *args) {
char *ptr = "thread2_proc";
pthread_setspecific(key, ptr);
print_tread2_key();
}
struct pair() {
int x;
int y;
}
void *thread3_proc(void *args) {
struct pair p = {1,2};
pthread_setspecific(key, &p);
print_tread3_key();
}
int main(){
pthread_t thid[10]={0};
int count = 0;
pthread_mutex_init(&mutex, NULL);
pthread_key_create(&key, NULL);
thread_cb_callback[THREAD_COUNT] = {//运行三个线程
thread1_proc,
thread2_proc,
thread3_proc
}
int i=0;
for(i=0;i<THREAD_COUNT;i++){
ptread_create(&thid[i], NULL, callback[i], &count);
}
for(i=0;i<THREAD_COUNT;i++){
ptread_join(thid[i], NULL);
}
return 0;
}
我们来看下运行结果
五、cpu的亲缘性
让cpu做一个粘合的作用,更大地利用cpu的性能
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define __USE_GNU
#include <sched.h>
#include<unistd.h>
#include<pthread.h>
#include<setjmp.h>
#include <sys/syscall.h>
jmp__buf env;//
int count = 0;
void process_affinity(int num) {
//gettid();
pid_t selfid = syscall(__NR_gettid);//返回进程id
cpu_set_t mask;//bit set
CPU_ZERO(&mask);
CPU_SET(selfid%num, &mask);
//第一个参数进程id号,第二个是mask的大小,第三个参数是绑定的cpu
sched_setaffinity(selid, sizeof(mask), &mask);
while(1);
}
int main(){
int num = sysconf(_SC_NPROCESSORS_CONF);//cpu的数量
int i = 0;
pid_t pid = 0;
for (i=0;i<num/2;i++) {//比如说num=8,那么我们就创建4个进程
pid = fork();
if(pid<=(pid_t)0) {
break;
}
}
if(pid==0) {
process_affinity(num);//黏合
}
while(1) usleep(1);
return 0;
}
运行结果,8核的cpu,粘合之后有4个核是满的占用率100%,粘合的作用在于能够更大程度去利用CPU
六、setjmp/longjmp
longjmp是c++中唯一一个可以破坏栈的,执行不用出栈
setjmp一设置,运行到longjmp以后,直接跳转到setjmp这里去
//创建10个线程
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<setjmp.h>
jmp__buf env;//
int count = 0;
void sub_func(int idx) {
printf("sub_func:%d\n",idx);
longjmp(env, idx);//执行到这一步直接跳到setjmp(env);这一行
}
int main(){
count = setjmp(env);//正常状态下返回0,如果有longjmp返回值由longjmp的第二个参数决定
if(count==0) {
sub_func(++count);
} else if(count==1) {
sub_func(++count);
} else if(count==2) {
sub_func(++count);
} else if(count==3) {
sub_func(++count);
}else {
printf("other items\n");
}//从count=1开始依次往下运行
return 0;
}
运行结果
这里我们就能引出try-catch是怎么做的
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<setjmp.h>
jmp__buf env;//
int count = 0;
#define Try count = setjmp(env); if(count==0)
#define Catch (type) else if(count == type)
#define Throw(type) longjmp(env, type);
#define Finally
void sub_func(int idx) {
printf("sub_func:%d\n",idx);
Throw(idx);
}
int main(){
Try{
sub_func(++count);
} Catch(1) {
sub_func(++count);
} Catch(2) {
sub_func(++count);
} Catch(3) {
sub_func(++count);
} Finally{
printf("other items\n");
}
return 0;
}
但是我们引出了以下两个问题需要后续好好讨论思考一下
1、嵌套try怎么解决
我想到了用链表实现try嵌套
2、线程安全怎么解决
我想到了线程的私有空间
未完待续。。。