• 线程,进程间的通讯和同步原理,实现用例和应用


    线程/进程间的通讯方式

    使用全局变量/共享内存
    使用thread中的lParam参数
    使用socket
    使用窗口和消息
    使用命名管道/匿名管道
    使用cmd参数
    使用environment变量

    线程的启动,退出和lParam参数通讯

    VC:
    
    #include <windows.h>
    
    DWORD WINAPI ThreadProc(LPVOID lParam);
    
    DWORD dwThreadId;
    HANDLE hThread = CreateThread(NULL, 0, ThreadProc, lParam, 0, &dwThreadId);
    
    ::TerminateThread(hThread, 0) //杀死线程,强烈不推荐使用!
    ::WaitForSingleObject(hThread, INFINITE) //等待线程退出
    ::CloseHandle(hThread); //不再关心该线程
    ::GetExitCodeThread (); //获取线程退出代码
    
    .Net:
    
    Using System.Threading;
    
    Static void ThreadProc(Object lParam)
    
    Object lParam = null;
    Thread th = new Thread(new ParameterizedThreadStart(ThreadProc));
    th.Start(lParam);
    
    th.IsBackground = true; // 主循环结束后依靠.Net运行机制自动取消该线程
    th.Join(); //等待线程退出
    th.Abort(); //杀死线程
    


    进程的启动,退出,命令行和环境变量

    VC:
    
    #include <windows.h>
    
    STARTUPINFO si = sizeof(STARTUPINFO) };
    PROCESS_INFORMATION ps;
    
    Char* pFileName = …;
    Char* pArgs = …;
    LPVOID pEnv = NULL;
    BOOL bRet = CreateProcess(pFileName, pArgs, …, pEnv, NULL, &si, &ps);
    
    ::TerminateProcess(ps.hProcess, 0) //杀死进程,不推荐使用,但比TerminateThread强很多。
    ::WaitForSingleObject(ps.hProcess, INFINITE) //等待进程退出
    ::CloseHandle(ps.hProcess); // 不再关心该进程
    ::GetExitCodeProcess(); //获取进程退出代码
    
    .Net:
    
    Using System.Diagnotics;
    
    Process p = new Process();
    p.StartInfo = new ProcessStartInfo();
    p.StartInfo.FileName = …;
    p.StartInfo.Arguments = …;
    p.StartInfo.EnvironmentVariables = …;
    p.Start();
    
    p.WaitForExit(); //等待进程退出
    p.Kill(); //杀死进程
    p.ExitCode // 获取进程退出代码


    管道通讯

    创建匿名管道(读管道和写管道)
    BOOL WINAPI CreatePipe(
    		PHANDLE hReadPipe,  
    		PHANDLE hWritePipe, 
    		LPSECURITY_ATTRIBUTES lpPipeAttributes,
    		DWORD nSize); 
    
    销毁匿名管道
    BOOL WINAPI CloseHandle(
    		HANDLE hPipe
    	);
    
    管道通讯用例NPSample


    共享内存(VC)

    CreateFileMapping
    MapViewOfFile
    UnmapViewOfFile
    CloseHandle
    共享内存实现用例ShmSample
    


    线程 / 进程间的同步

    有了以上进程通讯的方式,必然会产生同步问题。
    同步的几种方式:
    	临界区CriticalSection --- 轻量级代码关键段
    	互斥锁Mutex --- 互斥锁,只能被一个进程拥有
    	信号量Semaphore  --- 信号灯,
       事件Event    ---- 一次性手动或自动事件
       原子变量Atomic   ---- 保证一个变量值唯一性
       自旋锁SpinLock  ---- 用户态自旋锁,适合短代码加锁
       
       都可以跨进程使用,但临界区跨进程必须放于共享内存中。
    


    CriticalSection

    VC:
    
    #include <windows.h>
    
    CRITICAL_SECTION sec;
    
    InitializeCriticalSection(&sec); //初始化临界区
    InitializeCriticalSectionAndSpinCount(&sec, 2000); //自旋方式初始化临界区
    DeleteCriticalSection(&sec); //删除临界区
    EnterCritcalSection(&sec); //进入临界区
    LeaveCriticalSection(&sec); //离开临界区
    TryEnterCriticalSection(&sec); //试图进入临界区,进入成功要离开,否则返回FALSE
    
    注意:尽量使用类锁(自动析构)如有性能或者防止死锁需要,尽量不使用return,防止离开时忘记释放锁
    
    .Net:
    
    String s;
    lock (s) // 锁定对象s
    {
        ….;
    }
    
    System.Threading.Motitor.Enter(s);
    
    System.Threading.Motitor.Leave(s); //尽量加到finally块里面,避免抛出异常导致锁未释放
    


    Mutex

    VC:
    
    #include <windows.h>
    
    HANDLE mutex;
    
    mutex = CreateMutex(NULL,…); //初始化匿名互斥锁
    Mutex = CreateMutex(“123”, …); //初始化有名称互斥锁
    Mutex = OpenMutex(“123”, …); //打开有名称互斥锁
    CloseHandle(mutex); //关闭互斥锁;
    WaitForSingleObject(mutex, waitTime); // 等待互斥锁,第二个参数为等待时间
    ReleaseMutex(mutex); //释放互斥锁;
    注意:尽量使用类锁(自动析构)如有性能或者防止死锁需要,尽量不使用return,防止离开时忘记释放锁
    注意:线程/进程退出时互斥锁将自动被释放,其他等待的线程/进程将获得该锁并返回WAIT_ABANDONED.
    
    .Net:
    
    Using System.Threading;
    
    Mutex m = new Mutex();
    Mutex m = new Mutex(“123”, …); //初始化有名称互斥锁
    m.WaitOne(); // 等待互斥锁
    m.ReleaseMutex(); // 释放互斥锁,尽量加到finally块里面,避免抛出异常导致锁未释放
    


    Semaphore

    VC:
    
    #include <windows.h>
    
    HANDLE sem;
    
    sem = CreateSemaphore(NULL,…); //初始化匿名信号量并初始化初始信号数量
    sem = CreateSemaphore(“123”, …); //初始化有名称信号量
    sem = OpenSemaphore(“123”, …); //打开有名称信号量
    CloseHandle(sem); //关闭信号量;
    WaitForSingleObject(sem, waitTime); // 等待信号量,第二个参数为等待时间,若成功,信号量计数-1
    ReleaseSemaphore(sem, count); //将信号量计数增加count;
    注意:尽量使用类锁(自动析构)如有性能或者防止死锁需要,尽量不使用return,防止离开时忘记释放锁
    注意:线程/进程退出时信号量将不会被释放,其他等待的线程/进程将依然会锁住
    
    .Net:
    
    Using System.Threading;
    
    Semaphore m = new Semaphore();
    Semaphore m = new Semaphore(“123”, …); //初始化有名称信号量
    m.WaitOne(); // 等待信号量并将信号量数目-1
    m.Release (count); // 信号量计数增加count,尽量加到finally块里面,避免抛出异常导致锁未释放
    


    利用 Semaphore 实现控制队列

    Semaphore和queue一起用,可完成完整的加锁队列,
    消费者每次取数据时等待信号量,等待成功后取数据(加锁)。
    生产者每次生产数据时先将数据入队(加锁),并将信号量计数+1。

    Event

    VC:
    
    #include <windows.h>
    
    HANDLE eve;
    
    eve = CreateEvent(NULL,…); //初始化匿名事件并初始化状态和工作方式
    eve = CreateEvent(“123”, …); //初始化有名称事件
    eve = OpenEvent(“123”, …); //打开有名称事件
    CloseHandle(eve); //关闭事件;
    WaitForSingleObject(eve, waitTime); // 等待事件,第二个参数为等待时间,若成功,如工作方式为自动重置,事件将自动被重置
    SetEvent(eve); // 设置事件为有信号状态
    ResetEvent(eve); // 设置事件为无信号状态
    注意:尽量使用类锁(自动析构)如有性能或者防止死锁需要,尽量不使用return,防止离开时忘记释放锁
    注意:线程/进程退出时信号量将不会被释放,其他等待的线程/进程将依然会锁住
    
    .Net:
    
    Using System.Threading;
    
    EventHandle m = new EventHandle ();
    EventHandle m = new EventHandle (“123”, …); //初始化有名称信号量
    m.WaitOne(); // 等待信号量并将信号量数目-1
    m.Release (count); // 信号量计数增加count,尽量加到finally块里面,避免抛出异常导致锁未释放
    


    利用Event控制线程运行(VC)

    看过很多线程代码是这样写的
    DWORD WINAPI ThreadProc(LPVOID param)
    {
    	while (m_bRun)
    	{
    		DoWork(…);
    		Sleep(1);
           }
    }
    Void Stop()
    {
    	m_bRun = FALSE;
    	::WaitForSingleObject(…);
    }
    
    实际应该这样优化:
    DWORD WINAPI ThreadProc(LPVOID param)
    {
    	while (TRUE)
    	{
    	DWORD dwRet = WaitForSingleObject(hEvent, 	1);
    	if (dwRet == WAIT_OBJECT_0)
    		break;
    	DoWork(…);
    	Sleep(1);
           }
    }
    
    Void Stop()
    {
    	SetEvent(hEvent);
    	::WaitForSingleObject(…);
    }
    


    原子变量

    原子变量的原理

    原子变量的原理是利用硬件支持锁定某块内存的功能实现,就算有多个CPU同时访问该段内存,也只有一个能进入该内存,其他CPU将被锁住。

    由于原子变量并非对代码段加锁,而是对数据区加锁,并且锁的空间很小,因此一般只适合数量上的(引用计数)或者数值上的(个数,次数)的加锁。

    VC: InterlockedIncrement, InterlockedExchangeAdd, InterlockedDecrement, …

    .Net: System.Threading.Interlocked类

    自旋锁

    自旋锁是用户态锁,利用锁定某块内存的方式不断读取该块内存数据来加锁/解锁,工作机理和原子变量类似,但要注意自旋锁仅适合非单核CPU(单核在用户态自旋是没有意义的)和较短代码段的锁,若锁的时间过长将引起大量的CPU耗损。

    同步与死锁

    死锁原因
    忘记在某个地方释放锁
    使用TerminateThread/TerminateProcess导致锁对象未释放
    加锁未按顺序加锁
    锁太多,不知该如何加锁
    每个锁的锁周期要短,不要对非关键代码区域段加锁
    每个锁的目的要明确,不要一个锁去锁太多的对象和元素
    加锁要按顺序加锁
    注意SendMessage类函数和回调函数的加锁,确保在调用之前已经释放了应该释放的锁
  • 相关阅读:
    如何优雅地用Redis实现分布式锁?
    redis 持久化有几种方式?
    怎么保证缓存和数据库数据的一致性?
    jedis 和 redisson 有哪些区别?
    redis支持哪些数据类型?redis命令大全
    什么是redis的缓存雪崩与缓存穿透?如何解决?
    redis 为什么是单线程的?
    什么是memecache?redis 和 memecache 有什么区别?
    Redis入门到精通(九)——Redis数据库基本操作(切换数据库,数据移动,删除数据)
    Redis入门到精通(八)——key通用指令基本操作、key扩展操作(时效性控制、查询模式)、key其他操作(为key改名)
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/2985568.html
Copyright © 2020-2023  润新知