同步和互斥
当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。
所谓同步,是指在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
多线程同步和互斥有几种实现方法
线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
内核模式下的方法有:事件,信号量,互斥量。
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
进程间通信方式
(1)管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
(2)信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
(3)消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。
(4)共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
(5)信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。
(6)套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
C++多线程几种实现
windows下通过CreateThread
头文件 <windows.h>
创建线程API:
1 HANDLE CreateThread( 2 __in SEC_ATTRS 3 SecurityAttributes, //线程安全属性 4 __in ULONG 5 StackSize, // 堆栈大小 6 __in SEC_THREAD_START 7 StartFunction, // 线程函数 8 __in PVOID 9 ThreadParameter, // 线程参数 10 __in ULONG 11 CreationFlags, // 线程创建属性 12 __out PULONG 13 ThreadId // 线程ID 14 );
使用步骤:
HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL); CloseHandle(hThread);
互斥API:互斥量实现
1 HANDLE CreateMutex( 2 LPSECURITY_ATTRIBUTES lpMutexAttributes, 3 BOOL bInitialOwner, // 指定该资源初始是否归属创建它的进程 4 LPCTSTR lpName // 指定资源的名称 5 ); 6 7 BOOL ReleaseMutex( 8 HANDLE hMutex // 该函数用于释放一个独占资源,进程一旦释放该资源,该资源就不再属于它了 9 ); 10 11 DWORD WaitForSingleObject( 12 HANDLE hHandle, // 指定所申请的资源的句柄 13 DWORD dwMilliseconds // 一般指定为INFINITE,表示如果没有申请到资源就一直等待该资源 14 );
互斥API:临界区实现
1 CRITICAL_SECTION cs; 2 InitializeCriticalSection(&cs); 3 EnterCriticalSection(&cs); 4 LeaveCriticalSection(&cs); 5 DeleteCriticalSection(&cs);
例子:
1 #include <iostream> 2 #include <windows.h> 3 using namespace std; 4 5 HANDLE hMutex; 6 7 DWORD WINAPI Fun(LPVOID lpParamter) 8 { 9 while (1) { 10 WaitForSingleObject(hMutex, INFINITE); 11 cout << "Fun display!" << endl; 12 Sleep(1000); 13 ReleaseMutex(hMutex); 14 } 15 } 16 17 int main() 18 { 19 HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL); 20 hMutex = CreateMutex(NULL, FALSE, "screen"); 21 CloseHandle(hThread); 22 while (1) { 23 WaitForSingleObject(hMutex, INFINITE); 24 cout << "main display!" << endl; 25 Sleep(2000); 26 ReleaseMutex(hMutex); 27 } 28 29 return 0; 30 }
linux下则pthread库(POSIX)
头文件<phread.h>
创建线程API:
1 int pthread_create(pthread_t *tidp, //第一个参数为指向线程标识符的指针。 2 const pthread_attr_t *attr, //第二个参数用来设置线程属性。 3 (void*)(*start_rtn)(void*), //第三个参数是线程运行函数的起始地址。 4 void *arg //最后一个参数是运行函数的参数 5 ); 6 7 //线程通过调用pthread_exit函数终止执行 8 void pthread_exit(void* retval); 9 10 //函数pthread_join用来等待一个线程的结束,线程间同步的操作,阻塞函数 11 int pthread_join(pthread_t thread, void **retval);
例子:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <sched.h> 4 5 static int run = 1; 6 static int retvalue; 7 8 void *start_routine(void *arg) 9 { 10 int *running = arg; 11 printf("子线程初始化完毕,传入参数为:%d ", *running); 12 while (*running) 13 { 14 printf("子线程正在运行 "); 15 usleep(1); 16 } 17 printf("子线程退出 "); 18 19 retvalue = 8; 20 pthread_exit((void*)&retvalue); 21 } 22 23 int main(void) 24 { 25 pthread_t pt; 26 int ret = -1; 27 int times = 3; 28 int i = 0; 29 int *ret_join = NULL; 30 31 ret = pthread_create(&pt, NULL, (void*)start_routine, &run); 32 if (ret != 0) 33 { 34 printf("建立线程失败 "); 35 return 1; 36 } 37 usleep(1); 38 for (; i < times; i++) 39 { 40 printf("主线程打印 "); 41 usleep(1); 42 } 43 run = 0; 44 pthread_join(pt, (void*)&ret_join); 45 printf("线程返回值为:%d ", *ret_join); 46 return 0; 47 48 }
互斥步骤:
互斥锁:等价于二元信号量
- pthread_mutex_init初始化一个锁
- pthread_mutex_destory销毁互斥锁
- pthread_mutex_lock原子方式给加锁,如果自身线程对自身加锁的锁再次加锁,则死锁;其余线程加锁则不会死锁,但会阻塞。
- pthread_mutex_trylock非阻塞加锁
- pthread_mutex_unlock原子操作方式来解锁
注意:锁分为递归锁和非递归锁,递归锁可以对自身加锁的锁加锁多次。
pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex);
生产者消费者例子:(仅仅互斥,没有用信号量同步)

1 /* 2 * ex04-mutex.c 3 * 线程实例 4 */ 5 #include <stdio.h> 6 #include <pthread.h> 7 #include <sched.h> 8 9 10 void *producter_f (void *arg); 11 void *consumer_f (void *arg); 12 13 14 int buffer_has_item=0; 15 pthread_mutex_t mutex; 16 17 int running =1 ; 18 19 int main (void) 20 { 21 pthread_t consumer_t; 22 pthread_t producter_t; 23 24 pthread_mutex_init (&mutex,NULL); 25 26 pthread_create(&producter_t, NULL,(void*)producter_f, NULL ); 27 pthread_create(&consumer_t, NULL, (void *)consumer_f, NULL); 28 usleep(1); 29 running =0; 30 pthread_join(consumer_t,NULL); 31 pthread_join(producter_t,NULL); 32 pthread_mutex_destroy(&mutex); 33 34 return 0; 35 } 36 37 void *producter_f (void *arg) 38 { 39 while(running) 40 { 41 pthread_mutex_lock (&mutex); 42 buffer_has_item++; 43 printf("生产,总数量:%d ",buffer_has_item); 44 pthread_mutex_unlock(&mutex); 45 } 46 } 47 48 void *consumer_f(void *arg) 49 { 50 while(running) 51 { 52 pthread_mutex_lock(&mutex); 53 buffer_has_item--; 54 printf("消费,总数量:%d ",buffer_has_item); 55 pthread_mutex_unlock(&mutex); 56 } 57 }
同步步骤:信号量 头文件<semaphore.h>
- sem_init函数用于初始化一个未命名的信号量
- sem_destory函数用于销毁信号量
- sem_wait函数以原子操作的方式将信号量的值减1,如果信号量的值为0,则阻塞。
- sem_trywait上面的非阻塞版本,如果信号量为0,立即返回-1,并设置errno为EAGIN
- sem_post将信号量的值加1
参见《Linux网络编程》
C11std::thread
参见:C11多线程