Linux线程同步必知,常用方法揭秘!
Posted Wayne_hzw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux线程同步必知,常用方法揭秘!相关的知识,希望对你有一定的参考价值。
一、为什么要线程同步
在Linux 多线程编程中,线程同步是一个非常重要的问题。如果线程之间没有正确地同步,就会导致程序出现一些意外的问题,例如:
- 竞态条件(Race Condition):多个线程同时修改同一个共享变量,可能会导致不可预测的结果,因为线程的执行顺序是不确定的。
- 死锁(Deadlock):当两个或多个线程互相等待对方释放资源时,可能会导致死锁,这会导致程序无法继续执行。
- 活锁(Livelock):当多个线程相互响应对方的动作,而没有任何进展时,可能会导致活锁,这也会导致程序无法继续执行。
- 两个人在走路时需要相互让路,两个人都想让对方先通过,但最终还是没有人通过,这就是一种活锁情况
接下来将介绍互斥锁、条件变量、信号量、读写锁这几种线程同步方法,并使用C语言代码示例说明其使用方法。
二、互斥锁
互斥锁是一种用于线程同步的锁,用于保护共享资源。只有拥有该锁的线程才能访问共享资源,其他线程需要等待锁被释放后才能继续执行。
在Linux环境下,我们可以使用pthread库提供的互斥锁函数来实现互斥锁机制。以下是一些常用的互斥锁函数:
函数名 | 描述 |
---|---|
pthread_mutex_init |
初始化互斥锁 |
pthread_mutex_lock |
加锁互斥锁 |
pthread_mutex_trylock |
尝试加锁互斥锁 |
pthread_mutex_unlock |
解锁互斥锁 |
pthread_mutex_destroy |
销毁互斥锁 |
初始化互斥锁
在使用互斥锁之前,需要先初始化互斥锁。pthread_mutex_init函数用于初始化一个互斥锁。函数原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
其中,mutex参数是一个指向pthread_mutex_t结构体的指针,用于指定要初始化的互斥锁;attr参数是一个指向pthread_mutexattr_t结构体的指针,用于指定互斥锁的属性,通常设置为NULL。
以下是一个初始化互斥锁的例子:
#include <pthread.h>
pthread_mutex_t mutex;
int main()
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// ...
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
加锁互斥锁
加锁互斥锁用于保证同一时刻只有一个线程能够访问共享资源。pthread_mutex_lock函数用于加锁一个互斥锁。函数原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
其中,mutex参数是一个指向pthread_mutex_t结构体的指针,用于指定要加锁的互斥锁。
以下是一个加锁互斥锁的例子:
#include <pthread.h>
pthread_mutex_t mutex;
void* thread_func(void* arg)
// 加锁互斥锁
pthread_mutex_lock(&mutex);
// 访问共享资源
// ...
// 解锁互斥锁
pthread_mutex_unlock(&mutex);
return NULL;
int main()
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建线程
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
// ...
// 等待线程结束
pthread_join(tid, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
尝试加锁互斥锁
尝试加锁互斥锁与加锁互斥锁的主要区别在于,如果互斥锁已经被其他线程锁定了,尝试加锁互斥锁将不会阻塞当前线程,而是会立即返回一个错误代码。函数原型如下:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
其中,mutex参数是一个指向pthread_mutex_t结构体的指针,用于指定要尝试加锁的互斥锁。
以下是一个尝试加锁互斥锁的例子:
#include <pthread.h>
pthread_mutex_t mutex;
void* thread_func(void* arg)
// 尝试加锁互斥锁
int ret = pthread_mutex_trylock(&mutex);
if (ret == 0)
// 访问共享资源
// ...
// 解锁互斥锁
pthread_mutex_unlock(&mutex);
else
// 互斥锁已经被其他线程锁定了
// ...
return NULL;
int main()
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建线程
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
// ...
// 等待线程结束
pthread_join(tid, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
解锁互斥锁
解锁互斥锁用于释放已经锁定的互斥锁。pthread_mutex_unlock函数用于解锁一个互斥锁。函数原型如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
其中,mutex参数是一个指向pthread_mutex_t结构体的指针,用于指定要解锁的互斥锁。
以下是一个解锁互斥锁的例子:
#include <pthread.h>
pthread_mutex_t mutex;
void* thread_func(void* arg)
// 加锁互斥锁
pthread_mutex_lock(&mutex);
// 访问共享资源
//
// 解锁互斥锁
pthread_mutex_unlock(&mutex);
return NULL;
销毁互斥锁
在不再需要使用互斥锁时,需要将互斥锁销毁。pthread_mutex_destroy函数用于销毁一个互斥锁。函数原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
其中,mutex参数是一个指向pthread_mutex_t结构体的指针,用于指定要销毁的互斥锁。
以下是一个销毁互斥锁的例子:
#include <pthread.h>
pthread_mutex_t mutex;
int main()
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// ...
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
示例程序
下面是一个简单的示例程序,演示了如何使用互斥锁来同步两个线程的访问。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mutex;
int shared_data = 0;
void *thread_func(void *arg)
int i;
for (i = 0; i < 1000000; i++)
pthread_mutex_lock(&mutex);
shared_data++;
pthread_mutex_unlock(&mutex);
return NULL;
int main()
pthread_t thread1, thread2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&thread1, NULL, thread_func, NULL);
pthread_create(&thread2, NULL, thread_func, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
printf("Shared data: %d\\n", shared_data);
return 0;
在这个程序中,thread_func
函数是两个线程执行的函数,它会对shared_data
变量进行1000000次加一操作。
为了确保多个线程不会同时访问shared_data
变量,我们使用了一个互斥锁。当一个线程要访问shared_data
变量时,它会调用pthread_mutex_lock
函数来加锁。如果锁已经被其他线程持有,那么这个线程就会被阻塞,直到锁被释放为止。当线程完成对shared_data
变量的操作后,它会调用pthread_mutex_unlock
函数来释放锁。
在这个程序执行完毕后,我们可以通过打印shared_data
变量的值来检查程序是否正确地同步了两个线程的访问。如果程序正确地同步了线程的访问,那么shared_data
变量的值应该是2000000。
Linux 线程同步的三种方法
线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。
一、互斥锁(mutex)
通过锁机制实现线程间的同步。
- 初始化锁。在Linux下,线程的互斥量数据类型是pthread_mutex_t。在使用前,要对它进行初始化。
静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态分配:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr); - 加锁。对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); - 解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex); - 销毁锁。锁在是使用完成后,需要进行销毁以释放资源。
int pthread_mutex_destroy(pthread_mutex *mutex);
- #include <cstdio>
- #include <cstdlib>
- #include <unistd.h>
- #include <pthread.h>
- #include "iostream"
- using namespace std;
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- int tmp;
- void* thread(void *arg)
- {
- cout << "thread id is " << pthread_self() << endl;
- pthread_mutex_lock(&mutex);
- tmp = 12;
- cout << "Now a is " << tmp << endl;
- pthread_mutex_unlock(&mutex);
- return NULL;
- }
- int main()
- {
- pthread_t id;
- cout << "main thread id is " << pthread_self() << endl;
- tmp = 3;
- cout << "In main func tmp = " << tmp << endl;
- if (!pthread_create(&id, NULL, thread, NULL))
- {
- cout << "Create thread success!" << endl;
- }
- else
- {
- cout << "Create thread failed!" << endl;
- }
- pthread_join(id, NULL);
- pthread_mutex_destroy(&mutex);
- return 0;
- }
- //编译:g++ -o thread testthread.cpp -lpthread
二、条件变量(cond)
互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
- 初始化条件变量。
静态态初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER;
动态初始化,int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); - 等待条件成立。释放锁,同时阻塞等待条件变量为真才行。timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
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_signal,pthread_cond_broadcast(激活所有等待线程)
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞 - 清除条件变量。无线程等待,否则返回EBUSY
int pthread_cond_destroy(pthread_cond_t *cond);
- #include <stdio.h>
- #include <pthread.h>
- #include "stdlib.h"
- #include "unistd.h"
- pthread_mutex_t mutex;
- pthread_cond_t cond;
- void hander(void *arg)
- {
- free(arg);
- (void)pthread_mutex_unlock(&mutex);
- }
- void *thread1(void *arg)
- {
- pthread_cleanup_push(hander, &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);
- sleep(4);
- }
- pthread_cleanup_pop(0);
- }
- void *thread2(void *arg)
- {
- while(1)
- {
- printf("thread2 is running\n");
- pthread_mutex_lock(&mutex);
- pthread_cond_wait(&cond, &mutex);
- printf("thread2 applied the condition\n");
- pthread_mutex_unlock(&mutex);
- sleep(1);
- }
- }
- int main()
- {
- pthread_t thid1,thid2;
- printf("condition variable study!\n");
- pthread_mutex_init(&mutex, NULL);
- pthread_cond_init(&cond, NULL);
- pthread_create(&thid1, NULL, thread1, NULL);
- pthread_create(&thid2, NULL, thread2, NULL);
- sleep(1);
- do
- {
- pthread_cond_signal(&cond);
- }while(1);
- sleep(20);
- pthread_exit(0);
- return 0;
- }
- #include <pthread.h>
- #include <unistd.h>
- #include "stdio.h"
- #include "stdlib.h"
- static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
- static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
- struct node
- {
- int n_number;
- struct node *n_next;
- }*head = NULL;
- static void cleanup_handler(void *arg)
- {
- printf("Cleanup handler of second thread./n");
- free(arg);
- (void)pthread_mutex_unlock(&mtx);
- }
- static void *thread_func(void *arg)
- {
- struct node *p = NULL;
- pthread_cleanup_push(cleanup_handler, p);
- while (1)
- {
- //这个mutex主要是用来保证pthread_cond_wait的并发性
- pthread_mutex_lock(&mtx);
- while (head == NULL)
- {
- //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何
- //这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线
- //程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。
- //这个时候,应该让线程继续进入pthread_cond_wait
- // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,
- //然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立
- //而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
- //用这个流程是比较清楚的
- pthread_cond_wait(&cond, &mtx);
- p = head;
- head = head->n_next;
- printf("Got %d from front of queue/n", p->n_number);
- free(p);
- }
- pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁
- }
- pthread_cleanup_pop(0);
- return 0;
- }
- int main(void)
- {
- pthread_t tid;
- int i;
- struct node *p;
- //子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而
- //不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大
- pthread_create(&tid, NULL, thread_func, NULL);
- sleep(1);
- for (i = 0; i < 10; i++)
- {
- p = (struct node*)malloc(sizeof(struct node));
- p->n_number = i;
- pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,
- p->n_next = head;
- head = p;
- pthread_cond_signal(&cond);
- pthread_mutex_unlock(&mtx); //解锁
- sleep(1);
- }
- printf("thread 1 wanna end the line.So cancel thread 2./n");
- //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出
- //线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。
- pthread_cancel(tid);
- pthread_join(tid, NULL);
- printf("All done -- exiting/n");
- return 0;
- }
三、信号量(sem)
如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。
- 信号量初始化。
int sem_init (sem_t *sem , int pshared, unsigned int value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。 - 等待信号量。给信号量减1,然后等待直到信号量的值大于0。
int sem_wait(sem_t *sem); - 释放信号量。信号量值加1。并通知其他等待线程。
int sem_post(sem_t *sem); - 销毁信号量。我们用完信号量后都它进行清理。归还占有的一切资源。
int sem_destroy(sem_t *sem);
- #include <stdlib.h>
- #include <stdio.h>
- #include <unistd.h>
- #include <pthread.h>
- #include <semaphore.h>
- #include <errno.h>
- #define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!/n", __func__);return;}
- typedef struct _PrivInfo
- {
- sem_t s1;
- sem_t s2;
- time_t end_time;
- }PrivInfo;
- static void info_init (PrivInfo* thiz);
- static void info_destroy (PrivInfo* thiz);
- static void* pthread_func_1 (PrivInfo* thiz);
- static void* pthread_func_2 (PrivInfo* thiz);
- int main (int argc, char** argv)
- {
- pthread_t pt_1 = 0;
- pthread_t pt_2 = 0;
- int ret = 0;
- PrivInfo* thiz = NULL;
- thiz = (PrivInfo* )malloc (sizeof (PrivInfo));
- if (thiz == NULL)
- {
- printf ("[%s]: Failed to malloc priv./n");
- return -1;
- }
- info_init (thiz);
- ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz);
- if (ret != 0)
- {
- perror ("pthread_1_create:");
- }
- ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz);
- if (ret != 0)
- {
- perror ("pthread_2_create:");
- }
- pthread_join (pt_1, NULL);
- pthread_join (pt_2, NULL);
- info_destroy (thiz);
- return 0;
- }
- static void info_init (PrivInfo* thiz)
- {
- return_if_fail (thiz != NULL);
- thiz->end_time = time(NULL) + 10;
- sem_init (&thiz->s1, 0, 1);
- sem_init (&thiz->s2, 0, 0);
- return;
- }
- static void info_destroy (PrivInfo* thiz)
- {
- return_if_fail (thiz != NULL);
- sem_destroy (&thiz->s1);
- sem_destroy (&thiz->s2);
- free (thiz);
- thiz = NULL;
- return;
- }
- static void* pthread_func_1 (PrivInfo* thiz)
- {
- return_if_fail(thiz != NULL);
- while (time(NULL) < thiz->end_time)
- {
- sem_wait (&thiz->s2);
- printf ("pthread1: pthread1 get the lock./n");
- sem_post (&thiz->s1);
- printf ("pthread1: pthread1 unlock/n");
- sleep (1);
- }
- return;
- }
- static void* pthread_func_2 (PrivInfo* thiz)
- {
- return_if_fail (thiz != NULL);
- while (time (NULL) < thiz->end_time)
- {
- sem_wait (&thiz->s1);
- printf ("pthread2: pthread2 get the unlock./n");
- sem_post (&thiz->s2);
- printf ("pthread2: pthread2 unlock./n");
- sleep (1);
- }
- return;
- }
转:http://blog.csdn.net/zsf8701/article/details/7844316
以上是关于Linux线程同步必知,常用方法揭秘!的主要内容,如果未能解决你的问题,请参考以下文章