1.linux使用多线程同步的方法
1)互斥锁:当线程A锁定了互斥变量时,线程B再去锁定时就会被挂起,直到A解锁。
注意:当线程要不断的去轮询检查某个条件以判断是否可以操作需同步的数据时,可使用条件变量提高效率。
demo如下:
#include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex; void *print_msg(void *arg) { int i = 0; pthread_mutex_lock(&mutex); //互斥锁加锁 for (i = 0; i < 20; i++) { printf(" i = %d\n", i); usleep(200); } pthread_mutex_unlock(&mutex); } int main() { pthread_t id1, id2; pthread_mutex_init(&mutex, NULL); pthread_create(&id1, NULL, print_msg, NULL); pthread_create(&id2, NULL, print_msg, NULL); pthread_join(id1, NULL); //使主线程等待该线程结束后才结束,否则主线程很快结束,该线程没有机会执行 pthread_join(id2, NULL); pthread_mutex_destroy(&mutex); return 0; }
2)信号量:实际是一个整数,只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减1。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加1,但是sem_wait返回之前还是会将此value值减1。
demo如下:
#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <semaphore.h> #include <string.h> #include <stdlib.h> void *thread_func(void* msg); sem_t sem; sem_t sem_add; #define MSG_SIZE 512 int main() { int res = -1; pthread_t thread; void *thread_result = NULL; char msg[MSG_SIZE]; res = sem_init(&sem, 0, 0); if (res == -1) { printf("sem init failed\n"); exit(-1); } res = sem_init(&sem_add, 0, 1); if (res == -1) { printf("sem_add init failed\n"); exit(-1); } res = pthread_create(&thread, NULL, thread_func, msg); if (res != 0) { printf("pthread_create failed\n"); exit(-1); } printf("input some text. Enter 'end' to finish...\n"); sem_wait(&sem_add); while(strcmp("end\n", msg) != 0) { if (strncmp(msg, "TEST", 4) == 0) { strcpy(msg, "copy_data\n"); sem_post(&sem); sem_wait(&sem_add); } fgets(msg, MSG_SIZE, stdin); sem_post(&sem); //sem信号量加1,让子线程开始执行 sem_wait(&sem_add); //sem_add信号量减1,等待子线程处理完成 } printf("waiting for thread to finish...\n"); res = pthread_join(thread, &thread_result); if (res != 0) { printf("pthread_join faild\n"); exit(-1); } printf("thread joined\n"); sem_destroy(&sem); sem_destroy(&sem_add); exit(0); return 0; } void* thread_func(void* msg) { char *ptr = (char*)msg; sem_wait(&sem); while(strcmp("end\n", ptr) != 0) { int i = 0; for (; ptr[i] != '\0'; ++i ) { if (ptr[i] >= 'a' && ptr[i] <= 'z') { ptr[i] -= 'a' - 'A'; } } printf("you input %d characters\n", i - 1); printf("to uppercase: %s\n", ptr); sem_post(&sem_add); sem_wait(&sem); } sem_post(&sem_add); pthread_exit(NULL); }
3)条件变量:经常和互斥锁一起使用,使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程会解开相应的互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此变量阻塞的线程,这些线程将重新锁定互斥锁并重新测试条件是否满足。
pthread_cont_init()
pthread_cont_destroy()
pthread_cont_wait() //线程解开mutex指向的锁并被条件变量阻塞
pthread_cont_timedwait() //多了时间参数,当时间过了以后,即使条件变量不满足,阻塞也被解除
pthread_cont_signal()/pthread_cont_broadcast //唤醒被条件变量阻塞的线程。
demo如下:
pthread1() { pthread_mutex_lock(lock_s); sum++; pthread_mutex_unlock(lock_s); if (sum >= 100) { pthread_cond_signal(&cond_sum_ready);//先发送一次 } } pthread2() { pthread_mutex_lock(lock_s); while(sum < 100) { pthread_cond_wait(&cond_sum_ready, &lock_s);//会先执行pthread_MUTEX_UNLOCK进行解锁,然后休眠 } sum = 0; pthread_mutex_unlock(lock_s); }
注意:最终哪个线程接收到信号,根据优先级来
4)读写锁:可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。
- 当读写锁是写加锁状态时,在这个锁被解锁前,所有试图对这个锁加锁的线程都会被阻塞;
- 当读写锁是读加锁状态时,其他线程可以读模式得到访问权,但是以写模式对它进行加锁的线程都将被阻塞;
- 当读写锁是在读模式加锁状态时,如果有其他线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,避免读模式锁长期占用,而写模式所长期阻塞;
读写锁适用于对数据读的次数比写的次数多的情况。
API接口:
初始化和销毁:
int pthread_rwlock_init();
int pthread rwlock_destroy();
读加锁、写加锁、解锁:
pthread_rwlock_rdlock();
pthread_rwlock_wrlock();
pthread_rwlock_unlock();
非阻塞获得读锁和写锁:
pthread_rwlock_tryrdlock();
pthread_rwlock_trywrlock();
demo如下
#include <stdio.h> #include <pthread.h> #include <unistd.h> #define Read_Num 2 pthread_rwlock_t lock; class Data { public: Data(int i, float f): I(i), F(f){} int GetI() { return I; } float GetF() { return F; } private: int I; float F; }; Data *pData = NULL; void *read(void *arg) { int *id = (int*)arg; while(true) { pthread_rwlock_rdlock(&lock); printf(" reader %d is reading data\n", *id); if (pData == NULL) { printf("data is NULL\n"); } else { printf("data: I = %d, F = %f\n", pData->GetI(), pData->GetF()); } pthread_rwlock_unlock(&lock); } pthread_exit(0); } void *write(void *arg) { while(true) { pthread_rwlock_wrlock(&lock); //写锁加锁后,解锁前,所有试图对该锁加锁的线程都会被堵塞 printf("writer is wiriting data:\n"); if (pData == NULL) { pData = new Data(2, 2.2); printf("writer is writing data: %d, %f\n", pData->GetI(), pData->GetF()); } else { delete pData; pData = NULL; printf("wirter free the data\n"); } pthread_rwlock_unlock(&lock); } pthread_exit(0); } int main() { pthread_t reader[Read_Num]; pthread_t writer; for (int i = 0; i < Read_Num; i++) { pthread_create(&reader[i], NULL, read, (void*)&i); } pthread_create(&writer, NULL, write, NULL); while(1) { sleep(1); } return 0; }
2.信号量和互斥锁之间的区别:
互斥锁用于互斥,对资源的访问是无序的,信号量用于同步,对资源的访问是有序的
互斥量的加锁和解锁必须由同一线程对应使用,而信号量可以由一个线程释放,另一个线程得到
3.什么情况下会产生死锁以及怎样避免死锁
比如线程A,对资源M加锁,去申请资源N;
线程B,对资源N加锁,去申请资源M;
此种情况下会产生死锁,要有效的避免死锁,可使用银行家算法:
线程A和B在使用资源的时候按照同一顺序,即加锁时按照同一顺序,这样就可以避免死锁。