Linux多线程编程:用同步对象编程之互斥锁使用
2014-08-30 08:54
295 查看
同步对象是内存中的变量,可以按照与访问数据完全相同的方式对其进行访问。不同进程中的线程可以通过放在由线程控制的共享内存中的同步对象互相通信。尽管不同进程中的线程通常互不可见,但这些线程仍可以互相通信。
同步对象还可以放在文件中。同步对象可以比创建它的进程具有更长的生命周期。
同步对象具有以下可用类型:
■ 互斥锁
■ 条件变量
■ 读写锁
■ 信号
同步的作用包括以下方面:
■ 同步是确保共享数据一致性的唯一方法。
■ 两个或多个进程中的线程可以合用一个同步对象。由于重新初始化同步对象会将对象的状态设置为解除锁定,因此应仅由其中的一个协作进程来初始化同步对象。
■ 同步可确保可变数据的安全性。
■ 进程可以映射文件并指示该进程中的线程获取记录锁。一旦获取了记录锁,映射此文件的任何进程中尝试获取该锁的任何线程都会被阻塞,直到释放该锁为止。
■ 访问一个基本类型变量(如整数)时,可以针对一个内存负荷使用多个存储周期。如果整数没有与总线数据宽度对齐或者大于数据宽度,则会使用多个存储周期。尽管这种整数不对齐现象不会出现在SPARCPlatform Edition体系结构上,但是可移植的程序却可能会出现对齐问题。
使互斥锁保持一致 pthread_mutex_consistent_np
锁定互斥锁 pthread_mutex_lock
解除锁定互斥锁 pthread_mutex_unlock
使用非阻塞互斥锁锁定 pthread_mutex_trylock
销毁互斥锁 pthread_mutex_destroy
C++ Code
避免此问题的最佳方法是,确保线程在锁定多个互斥锁时,以同样的顺序进行锁定。如果始终按照规定的顺序锁定,就不会出现死锁。此方法称为锁分层结构,它通过为互斥锁指定逻辑编号来对这些锁进行排序。另外,请注意以下限制:如果您持有的任何互斥锁其指定编号大于n,则不能提取指定编号为n的互斥锁。但是,不能始终使用此方法。有时,必须按照与规定不同的顺序提取互斥锁。要防止在这种情况下出现死锁,请使用pthread_mutex_trylock()。如果线程发现无法避免死锁时,该线程必须释放其互斥锁。
线程1执行的代码段如下所示:
C++ Code
线程2执行的代码段如下所示:
C++ Code
在示例4–3中,线程1按照规定的顺序锁定互斥锁,但是线程2不按顺序提取互斥锁。要确保不会出现死锁,线程2必须非常小心地提取互斥锁1。如果线程2在等待该互斥锁释放时被阻塞,则线程2可能刚才已经与线程1进入了死锁状态。要确保线程2不会进入死锁状态,线程2需要调用pthread_mutex_trylock(),此函数可在该互斥锁可用时提取它。如果该互斥锁不可用,线程2将立即返回并报告提取失败。此时,线程2必须释放互斥锁2。线程1现在会锁定互斥锁2,然后释放互斥锁1和互斥锁2。
C++ Code
本示例针对每个包含一个互斥锁的节点使用单链接列表结构。要将某个节点从列表中删除,请首先从ListHead开始搜索列表,直到找到所需的节点为止。ListHead永远不会被删除。
要防止执行此搜索时产生并发删除,请在访问每个节点的任何内容之前先锁定该节点。由于所有的搜索都从ListHead开始,并且始终按照列表中的顺序提取锁,因此不会出现死锁。
因为更改涉及到两个节点,所以找到所需的节点之后,请锁定该节点及其前序节点。因为前序节点的锁总是最先提取,所以可再次防止出现死锁。示例4–5说明如何使用C代码来删除单链接列表中的项。
示例4–5单链接列表和嵌套锁定
C++ Code
C++ Code
以下的C代码用来获取两个节点上的锁并执行涉及到这两个锁的操作。
C++ Code
处理输入操作的线程在接收用户输入信息时不能被中断,因此需要先使用互斥锁获得资源占用,阻塞其他线程的执行。
输出线程中为了保证暑促不被中断,同时在输入信息前后需要先锁定互斥锁,操作完成后释放互斥锁。使用互斥锁就能很好地实现两个线程之间的同步机制。其源代码如下;
C++ Code
同步对象还可以放在文件中。同步对象可以比创建它的进程具有更长的生命周期。
同步对象具有以下可用类型:
■ 互斥锁
■ 条件变量
■ 读写锁
■ 信号
同步的作用包括以下方面:
■ 同步是确保共享数据一致性的唯一方法。
■ 两个或多个进程中的线程可以合用一个同步对象。由于重新初始化同步对象会将对象的状态设置为解除锁定,因此应仅由其中的一个协作进程来初始化同步对象。
■ 同步可确保可变数据的安全性。
■ 进程可以映射文件并指示该进程中的线程获取记录锁。一旦获取了记录锁,映射此文件的任何进程中尝试获取该锁的任何线程都会被阻塞,直到释放该锁为止。
■ 访问一个基本类型变量(如整数)时,可以针对一个内存负荷使用多个存储周期。如果整数没有与总线数据宽度对齐或者大于数据宽度,则会使用多个存储周期。尽管这种整数不对齐现象不会出现在SPARCPlatform Edition体系结构上,但是可移植的程序却可能会出现对齐问题。
1、使用互斥锁
初始化互斥锁 pthread_mutex_init使互斥锁保持一致 pthread_mutex_consistent_np
锁定互斥锁 pthread_mutex_lock
解除锁定互斥锁 pthread_mutex_unlock
使用非阻塞互斥锁锁定 pthread_mutex_trylock
销毁互斥锁 pthread_mutex_destroy
2、互斥锁示例代码
两个函数将互斥锁用于不同目的。increment_count()函数使用互斥锁确保对共享变量进行原子更新。get_count()函数使用互斥锁保证以原子方式读取64位数量count。在32位体系结构上,long long实际上是两个32位数量。C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <pthread.h> //互斥锁使用示例 pthread_mutex_t count_mutex; long long count; void increment_count() { pthread_mutex_lock(&count_mutex); count = count + 1; pthread_mutex_unlock(&count_mutex); } long long get_count() { long long c; pthread_mutex_lock(&count_mutex); c = count; pthread_mutex_unlock(&count_mutex); return (c); } |
2、锁分层结构的使用示例
有时,可能需要同时访问两个资源。您可能正在使用其中的一个资源,随后发现还需要另一个资源。如果两个线程尝试声明这两个资源,但是以不同的顺序锁定与这些资源相关联的互斥锁,则会出现问题。例如,如果两个线程分别锁定互斥锁1和互斥锁2,则每个线程尝试锁定另一个互斥锁时,将会出现死锁。示例4–2说明了可能的死锁情况。避免此问题的最佳方法是,确保线程在锁定多个互斥锁时,以同样的顺序进行锁定。如果始终按照规定的顺序锁定,就不会出现死锁。此方法称为锁分层结构,它通过为互斥锁指定逻辑编号来对这些锁进行排序。另外,请注意以下限制:如果您持有的任何互斥锁其指定编号大于n,则不能提取指定编号为n的互斥锁。但是,不能始终使用此方法。有时,必须按照与规定不同的顺序提取互斥锁。要防止在这种情况下出现死锁,请使用pthread_mutex_trylock()。如果线程发现无法避免死锁时,该线程必须释放其互斥锁。
线程1执行的代码段如下所示:
C++ Code
1 2 3 4 5 6 7 | //线程1运行的代码段 pthread_mutex_lock(&m1); pthread_mutex_lock(&m2); /* no processing */ pthread_mutex_unlock(&m2); pthread_mutex_unlock(&m1); |
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 | for (; ;) { pthread_mutex_lock(&m2); if(pthread_mutex_trylock(&m1) == 0) //使用trylock防止死锁的产生 /* got it */ break; /* didn’t get it */ pthread_mutex_unlock(&m2); } /* get locks; no processing */ pthread_mutex_unlock(&m1); pthread_mutex_unlock(&m2); |
3、嵌套锁定和单链接列表的结合使用示例
示例4–4和示例4–5说明了如何同时提取三个锁。通过按照规定的顺序提取锁可避免出现死锁。
示例4–4单链接列表结构C++ Code
1 2 3 4 5 6 7 8 9 | typedef struct node1 { int value; //值 struct node1 *link; //链接 pthread_mutex_t lock; //锁 } node1_t; node1_t ListHead; |
要防止执行此搜索时产生并发删除,请在访问每个节点的任何内容之前先锁定该节点。由于所有的搜索都从ListHead开始,并且始终按照列表中的顺序提取锁,因此不会出现死锁。
因为更改涉及到两个节点,所以找到所需的节点之后,请锁定该节点及其前序节点。因为前序节点的锁总是最先提取,所以可再次防止出现死锁。示例4–5说明如何使用C代码来删除单链接列表中的项。
示例4–5单链接列表和嵌套锁定
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | node1_t *delete(int value) //删除某些值 { node1_t *prev, *current; //先后指针 prev = &ListHead; //链表头 pthread_mutex_lock(&prev->lock); //锁定头结点,防止别的程序进入 while ((current = prev->link) != NULL) { pthread_mutex_lock(¤t->lock); //加锁处理 if (current->value == value) //进行删除 { prev->link = current->link; pthread_mutex_unlock(¤t->lock); pthread_mutex_unlock(&prev->lock); current->link = NULL; return(current); } pthread_mutex_unlock(&prev->lock); prev = current; } pthread_mutex_unlock(&prev->lock); return(NULL); } |
4、嵌套锁定和单链接列表的结合使用示例
示例4–6通过将以前的列表结构转换为循环列表来对其进行修改。由于不再存在用于标识的头节点,因该线程可以与特定的节点相关联,并可针对该节点及其邻居执行操作。锁分层结构在此处不适用,因为链接之后的分层结构明显是循环结构。C++ Code
1 2 3 4 5 6 7 | //示例4–6循环链接列表结构 typedef struct node2 { int value; struct node2 *link; pthread_mutex_t lock; } node2_t; |
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void Hit Neighbor(node2_t *me) { while (1) { pthread_mutex_lock(&me->lock); if (pthread_mutex_lock(&me->link->lock) != 0) { /* failed to get lock */ pthread_mutex_unlock(&me->lock); continue; } break; } me->link->value += me->value; me->value /= 2; pthread_mutex_unlock(&me->link->lock); pthread_mutex_unlock(&me->lock); } |
5、互斥锁应用实例
下面是一个使用互斥锁的应用,在此程序中,共有两个线程:一个线程负责从标准输入设备中读取数据存储在全局数据区,另一个线程负责将读入的数据输出到标识输出设备。处理输入操作的线程在接收用户输入信息时不能被中断,因此需要先使用互斥锁获得资源占用,阻塞其他线程的执行。
输出线程中为了保证暑促不被中断,同时在输入信息前后需要先锁定互斥锁,操作完成后释放互斥锁。使用互斥锁就能很好地实现两个线程之间的同步机制。其源代码如下;
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <string.h> #include <semaphore.h> void *thread_function(void *arg); pthread_mutex_t work_mutex; #define WORK_SIZE 1024 char work_area[WORK_SIZE]; int time_to_exit = 0; int main(int argc, char const *argv[]) { int res; pthread_t pthread; void *thread_result; res = pthread_mutex_init(&work_mutex, NULL); //初始化互斥锁 if (res != 0) { perror("pthread_mutex_init failed!"); exit(EXIT_FAILURE); } res = pthread_create(&pthread, NULL, thread_function, NULL); //创建新线程 if (res != 0) { perror("pthread_create failed!"); exit(EXIT_FAILURE); } pthread_mutex_lock(&work_mutex); //接收输入前,给互斥锁上锁 printf("input a text. Enter end to finish!\n"); while(!time_to_exit) //time_to_exit值由另一线程修改 { fgets(work_area, WORK_SIZE, stdin); //从stdin获取一行信息 pthread_mutex_unlock(&work_mutex); while(1) { pthread_mutex_lock(&work_mutex); if (work_area[0] != '\0') //检测内容是否已经修改 { pthread_mutex_unlock(&work_mutex); sleep(1); } else break; //如果已经输出,执行下一轮读入 } } pthread_mutex_unlock(&work_mutex); //解锁 // printf("\nwaiting..."); res = pthread_join(pthread, &thread_result); //等待线程结束 if (res != 0) { perror("pthread_join failed!"); exit(EXIT_FAILURE); } printf("son thread Exit success!\n"); pthread_mutex_destroy(&work_mutex); //摧毁互斥锁 exit(EXIT_SUCCESS); //成功退出 } void *thread_function(void *arg) //新建线程调用的函数 { sleep(1); pthread_mutex_lock(&work_mutex); //加锁 while(strncmp("end", work_area, 3) != 0) //是否为end,不断循环执行,不退出 { printf("you input %d characters\n", strlen(work_area) - 1); //打印 work_area[0] = '\0'; //in order to break the while of main thread pthread_mutex_unlock(&work_mutex); sleep(1); //in order to make the main thread get the lock pthread_mutex_lock(&work_mutex); } time_to_exit = 1; work_area[0] = '\0'; //重置 pthread_mutex_unlock(&work_mutex); //解锁 pthread_exit(NULL); //线程退出 } |
相关文章推荐
- Linux多线程编程之同步对象编程:条件变量
- Solaris2.4 多线程编程指南3--使用同步对象编程
- 多线程编程--使用同步对象编程
- Solaris2.4 多线程编程指南3--使用同步对象编程
- python多线程编程: 使用互斥锁同步线程
- python多线程编程: 使用互斥锁同步线程
- Solaris2.4 多线程编程指南3--使用同步对象编程
- 转帖多线程编程使用互斥锁同步线程
- Linux多线程编程之同步对象编程:读写锁
- python多线程编程: 使用互斥锁同步线程
- linux多线程编程--使用互斥锁的简单程序
- Linux多线程编程:对象同步之线程信号管理
- C++,多线程编程------使用同步对象编程(转)
- linux多线程编程之一多线程数据同步及相关api使用示例
- 使用同步对象编程
- 多线程编程------使用同步对象编程
- 线程: 同步对象的使用
- 使用SharePoint Server 2007搜索对象模型编程创建搜索查询
- 使用临界段实现优化的进程间同步对象-原理和实现
- 使用临界段实现优化的进程间同步对象-原理和实现