线程的同步机制

线程同步问题

所谓的同步就是描述A,B线程在各自执行的时候的一种时序关系。互斥,是指A,B线程对于临界资源的不可并行访问,从这个角度看,这两个线程在访问临界资源的时候也表现出来一种先后关系--和同步一样,所以也将互斥问题划为一种特殊的同步关系。

问题:如何同步的-->同步的机制

  1. Mutex(互斥锁),是用于串行化访问资源的全局对象。我们首先设置互斥对象,然后访问资源,最后释放互斥对象。在设置互斥对象时,如果另一个线程(或进程)试图设置相同的互斥对象,该线程将会停下来,直到前一个线程(或进程)释放该互斥对象为止。注意它可以由不同应用程序共享。使用方法如下:
1. 头文件:pthread.h
2. pthread_mutex_t
3. pthread_mutex_init(pthread_mutex_t* mutex, NULL);//NULL采用的是默认属性
	或者采用静态初始化的方式:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
4. pthread_mutex_lock(pthread_mutex_t* mutex);	//阻塞调用
   pthread_mutex_lock(pthread_mutex_t* mutex);	//非阻塞调用
   pthread_mutex_timelock(pthread_mutex_t* mutex, struct timespec* abs_timeout);	//超时阻塞
5. pthread_mutex_unlock(pthread_mutex_t* mutex);	//释放互斥锁
6. pthread_mutex_destroy(pthread_mutex_t* mutex);	//销毁线程锁

test 1

# include <pthread.h>
# include <unistd.h>
# include <string.h>
# include <stdio.h>
char buff[100];//buffer between producer and consumer
pthread_mutex_t mutex;

void* producer(void * set){
    while(1){
    pthread_mutex_lock(&mutex);//Lock
    strcpy(buff, (const char *)set);//write to buffer
    pthread_mutex_unlock(&mutex);//unlock
    sleep(4);//sleep 3s
    puts(buff);
    }
}

void* consumer(void* get){
    while(1){
    pthread_mutex_lock(&mutex);//Lock
    strcpy(buff, (const char *)get);//write to buffer
    pthread_mutex_unlock(&mutex);//unlock
    sleep(2);//sleep 2s
    puts(buff);
    }
}

int main()
{
    pthread_mutex_init(&mutex, NULL);// init the mutex
    pthread_t tid1,tid2;
    pthread_create(&tid1, NULL, producer, (void* )"hello, I'm producer. I've produced one\n");

    pthread_create(&tid2, NULL, consumer, (void* )"hello, I'm consumer. I've consumed one\n");

    puts("begin:\n");	
    //mode sync
    pthread_detach(tid1);
    pthread_detach(tid2);

    sleep(12);// 300s for this process
    puts("time up\n");
    puts("I'm main thread, I'm exiting\n");

    pthread_mutex_destroy(&mutex);
    return 0;
}

test 2

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex;
struct timespec out = {6, 0};//max wait_time
//struct timespec* outp = &out;
//outp->tv_sec = 6;
//outp->tv_nsec = 0;

void* print(void* str){
	pthread_mutex_timedlock(&mutex, &out);
	printf("working now, and %s",(char*)str);
	sleep(4);
	pthread_mutex_unlock(&mutex);
	return (void *)0;
}

int main()
{
	pthread_mutex_init(&mutex, NULL);//initial
	char* ptr[3]={"I'm thread1\n", "I'm thread2\n", "I'm thread3\n"};	
	pthread_t tid[3];
	int i;
	for(i=0; i<3; i++){	//create threads and choose mode join
		pthread_create(tid+i, NULL, print, (void* )ptr[i]);
		pthread_join( tid[i], NULL );
	}
	pthread_mutex_destroy(&mutex);//destroy mutex
	printf("HELLO, I'm MIAN THREAD, EXITING\n");
	return 0;
}

1.1 自旋锁: 和互斥锁类似,但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

1. pthread_spinlock_t spinlock = SPIN_LOCK_UNLOCKED; //定义一个自旋锁,并静态初始化
2. int pthread_spin_destroy(&spinlock);
3. int pthread_spin_init(&spinlock, int);
4. int pthread_spin_lock(&spinlock);
5. int pthread_spin_trylock(&spinlock);
6. int pthread_spin_unlock(&spinlock);

​ 1.2 读写锁: 读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作 。

1. #include <pthread.h>		//头文件
2. pthread_rwlock_t rwlock;	//定义读写锁
3. int pthread_rwlock_init(&rwlock, NULL);		//初始化,NULL为默认属性
4. int pthread_rwlock_rdlock(&rwlock);	//阻塞读加锁
5. int pthread_rwlock_wrlock(&rwlock);	//阻塞写加锁
6. int pthread_rwlock_tryrdlock(&rwlock);	//非阻塞读加锁
7. int pthread_wrlock_trywrlock(&rwlock);	//非阻塞写加锁
4. int pthread_rwlock_destroy(&rwlock);	//销毁

test

生产者消费者问题:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,所有生产者和消费者都是异步方式运行的,但它们必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经装满产品且尚未被取走的缓冲区中投放产品。

( 假设1个生产者,2个消费者,缓冲区大小为4。 )

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

pthread_rwlock_t rwlock;	//定义读写锁
sem_t full = 0;
sem_t empty = 4;

void* produce(void* arg){
    while(1){
        printf("producer:I wanna produce\n");
        sem_wait(&empty);
        pthread_rwlock_wrlock(&rwlock);
        printf("producer:I'm producing\n");
        sleep(4);
        pthread_rwlock_unlock(&rwlock);
        sem_post(&full);
        printf("producer:I've just produced one\n");
    }
}

void* consume(void* arg){
    while(1){
        printf("consumer%s:I wanna consume\n", );
        sem_wait(&full);
        pthread_rwlock_rdlock(&rwlock);
        printf("consumer%s:I'm consuming\n");
        sleep(2);
        pthread_rwlock_unlock(&rwlock);
        sem_post(&empty);
        printf("consumer%s:I've just consumed one\n");
    }
}

int main()
{
    pthread_t t1;
    //信号量初始化
    sem_init(&full, 0, 0);
    sem_init(&empty, 0, 4);
    //读写锁初始化
    pthread_rwlock_init(&rwlock, NULL);	
    
    pthread_create(&t1, NULL, produce, NULL);
    pthread_create(&t1, NULL, consume, (void*)"1");
    pthread_create(&t1, NULL, consume, (void*)"2");
    sleep(20);
    
    //销毁信号量和读写锁
    sem_destroy(&full);
    sem_destroy(&empty);
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

运行效果如下

  1. Semaphore(信号量),它与互斥锁相似,但它可以计数。例如可以允许一个给定资源同时同时被三个线程访问。其实Mutex就是最大计数为一的Semaphore。
1. sema_t;	//type
2. int sem_init(sem_t* sem, 0, int value);	//0是默认参数, value是sem的初始值
3. int sem_post(sem_t* sem);	//信号量的值加一
4. int sem_wait(sem_t* sem);	//信号量的值减一
5. int sem_destroy(sem_t* sem);	//销毁信号量
6. #include <semaphore.h>	//头文件
7. int sem_getvalue(sem_t *restrict sem, int *restrict valp);	//获取信号量的值

test 1

#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>

int buff = 3;
sem_t sem;
//生产者执行线程
void produce(void* arg){
    while(1){
        printf("producer:I'm producing...\n");
    	sem_post(&sem);
    	sleep(4);
    	printf("prodicer:I've just put one\n");
    }
}
//消费者执行线程
void consume(void* arg){
    while(1){
        printf("consumer:I'm consuming...\n");
    	sem_wait(&sem);
    	sleep(2);
    	printf("consumer:I've just consumed one\n");
    }
}
int main()
{
    sem_init(&sem, 0, buff);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, produce, NULL);
    pthread_create(&t2, NULL, consume, NULL);
    sleep(30);
    printf("Main:exiting...\n");
    sem_destroy(&sem);
    return 0;
}
test 2(三个缓冲区,一个生产者一个消费者)
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>

sem_t plate;	//缓冲池,这里相当于一个互斥锁
sem_t empty;	//empty缓冲区数量
sem_t full;		//full缓冲区数量

//生产者执行线程
void* produce(void* arg){
    while(1){
        printf("producer:I wanna produce\n");
    	sem_wait(&empty);	//占位空缓冲区
        sem_wait(&plate);	//占位缓冲池
    	sleep(4);
        printf("producer:I've just post one\n");
    	sem_post(&plate);	//释放缓冲池
        sem_post(&full);	//放东西进缓冲区
    }
}
//消费者执行线程
void* consume(void* arg){
	   while(1){
           printf("consumer:I wanna consume\n");
           sem_wait(&full);	//
           sem_wait(&plate);	//占位缓冲池
           sleep(2);
           printf("consumer:I've just consumed one\n");
           sem_post(&plate);	//释放缓冲池
           sem_post(&empty);	//释放空缓冲区
       }
}

int main()
{
    //初始化信号量,plate=1, empty=3, full=0
	sem_init(&plate, 0, 1);	
    sem_init(&empty, 0, 3);	
    sem_init(&full, 0, 0);	
    //创建线程,并初始化
    pthread_t t1, t2;
    pthread_create(&t1, NULL, produce, NULL);
    pthread_create(&t2, NULL, consume, NULL);
    sleep(10);
 	printf("Main:I'm exiting\n");
    return 0;
}

运行结果如下

                图1	运行初始为空的缓冲池

初始条件的改变:缓冲池中缓冲区的总数保持不变, 若刚开始一个缓冲区已经满了,则full = 1, empty = 2

程序重新运行效果如下:

                图2	运行初始为1的缓冲池
  1. 条件变量(condition_variable),等待某个条件的发生。
1. pthread_cond_t
2. pthread_cond_t cond = PTHREAD_COND_INITIALIZER;	//静态
   int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);	//动态
3. int pthread_cond_destroy(pthread_cond_t *cond);	//注销,没有变量等待的时候执行,否则EBUSY

test

# include <stdio.h>
# include <pthread.h>
# include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;	//条件变量常常和互斥锁一起使用

void* small(void* arg){
    printf("small:I'm small.\n");
    pthread_mutex_lock(&mutex);
    printf("small:I'm wating for someone\n");
    pthread_cond_wait(&cond, &mutex);
    printf("small:Lucky, I've get thing I want\n");
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

void* big(void* arg){
    printf("big:I'm big\n");
    pthread_cond_signal(&cond);
    printf("big:I've put sth, now you can go\n");
    pthread_exit(NULL);
}

int main()
{
    pthread_t tid;
    //	pthread_create(&tid, NULL, big, NULL );
    //pthread_detach();
    pthread_create(&tid, NULL, small, NULL);
    sleep(10);
    return 0;	
}
 
  1. Critical Sections(临界段),源代码中如果有不能由两个或两个以上线程同时执行的部分,可以用临界段来使这部分的代码执行串行化。它只能在一个独立的进程或一个独立的应用程序中使用。使用方法如下:
1. CRITICAL_SECTION g_cs;	//定义一个临界段
2. InializeCriticalSection();	//初始化临界段; 
3. DeleteCriticalSection();	//删除一个临界段; 
4. EnterCriticalSection();	//获取对临界段的所有权,独占共享资源; 
5. tryEnterCriticalSection();	//试图获得对临界段的所有权,但不阻塞; 
6. LeaveCriticalSection();	//释放对资源的所有权;

test

#include <stdio.h>
#include <windows.h>
#include <process.h>

CRITICAL_SECTION g_cs;//定义一个临界段
int var = -1;//临界段访问的资源

void fun1() {
	printf("fun1:I'm entering critical_section\n");
	EnterCriticalSection(&g_cs);
	var = 0;
	printf("fun1:I've set var = %d\n", var);
	LeaveCriticalSection(&g_cs);
	printf("fun1:I'm leaving critical_section\n");
}

void fun2() {
	printf("fun2:I'm entering critical_section\n");
	EnterCriticalSection(&g_cs);
	var = 1;
	printf("fun2:I've set var = %d\n", var);
	LeaveCriticalSection(&g_cs);
	printf("fun2:I'm leaving critical_section\n");
}


void main()
{
	printf("Main: at the beginning, var = %d\n",var);
        InializeCriticalSection();	//初始化临界段;
	_beginthread(fun1, 0, NULL);
	_beginthread(fun2, 0, NULL);
	Sleep(2000);
	printf("Main:exiting");
}

运行结果如下:

参考:
https://www.cnblogs.com/virusolf/p/4951251.html
https://blog.csdn.net/qq_17368865/article/details/80380586
https://blog.csdn.net/daaikuaichuan/article/details/82950711

posted @ 2020-08-26 16:43  solomarge  阅读(400)  评论(0)    收藏  举报