精华内容
下载资源
问答
  • 多进程、多线程同步(通讯)的方法
    千次阅读
    2020-07-14 13:03:30

    进程间通讯:

    • 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

    • 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

    • 高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。

    • 信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

    • 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

    • 信号 ( sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

    • 共享内存( sharedmemory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

    • 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

    注意:临界区则是一种概念,指的是访问公共资源的程序片段,并不是一种通信方式。

    线程通讯:

    • 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
    • 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
    • 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
    • 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
    • 信号机制(Signal): 类似进程间的信号处理

    提问:互斥锁与信号量的区别?
    答:互斥锁用于线程的互斥,信号量用于线程的同步。这是互斥锁和信号量的根本区别,也就是互斥和同步之间的区别。同时互斥锁的作用域仅仅在于线程,信号量可以作用于线程和进程。

    更多相关内容
  • 当使用多线程访问同一个资源的时候,非常容易出现线程安全的问题(例如,当多个线程同时对一个数据进行修改的时候,会导致某些线程对数据的修改丢失)。 因此,需要采用同步机制来解决这种问题。而Java主要提供了三...

    当使用多线程访问同一个资源的时候,非常容易出现线程安全的问题(例如,当多个线程同时对一个数据进行修改的时候,会导致某些线程对数据的修改丢失)。

    因此,需要采用同步机制来解决这种问题。而Java主要提供了三种实现同步机制的方法。今天我们就来认识一下~~

    一、synchronized关键字

    在Java语言中,每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程锁拥有,当一个线程调用对象的一段synchronized代码时,需要先获取这个锁,然后去执行相应的代码,执行结束之后,释放锁。

    而synchronized关键字主要有两种用法:synchronized方法synchronized块。此外,这个关键字还可以作用于静态方法、类或者某个实例,但这都对程序的效率有很大的影响

    1.synchronized方法。在方法的声明前主要有synchronized关键字,示例如下:

    public synchronized void mutiThreadAccess();

    只要把多个线程对类需要被同步的资源的操作放到mutiThreadAccess()方法中,就能保证这个方法在同一时刻只能被同一个线程访问,从而保证了多线程访问的安全性。然而,当一个方法的方法体规模非常大时,把该方法声明为synchronized会大大影响程序的执行效率。为了提高程序的效率,Java提供了synchronized块。

    2.synchronized块

    synchronized块既可以把任意的代码段声明为synchronized,也可以指定上锁的对象,有非常高的灵活性。其用法如下:

    synchronized(syncObject){
    
    //访问synchObject的代码
    
    }

    二、wait()方法与notify()方法

    当使用synchronized来修饰某个共享资源时,如果线程A1在执行synchronized代码,另一个线程A2也要同时执行同一个对象的同一synchronized代码时,线程A2将要等到线程A1执行完成之后,才能继续执行。在这种情况下可以使用wait()方法和notify()方法。

    在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁,进入等待状态,并且可以调用notify()方法或者notifyAll()方法通知正在等待的其他线程。notify()方法仅唤醒一个线程(即等待队列中的第一个线程),并允许它去获得锁,notifyAll()方法唤醒所有等待这个对象的线程并允许他们去获得锁,但并不是让所有唤醒线程都去获取到锁,而是让他们去竞争。

    三、Lock

    JDK5新增加了Lock接口以及它的一个实现类ReentrantLock(重入锁),Lock也可以用来实现多线程的同步。具体而言,它提供了如下一些方法来实现多线程的同步:

    1.lock()。

    这个方法以阻塞的方式获取锁,也就是说,如果获取到锁了,立即返回;如果别的线程持有锁,当前线程就等待,知道获取到锁之后返回。

    2.tryLock()。

    这个方法与lock()方法不同,它以非阻塞的方式来获取锁。此外,它只是常识性地去获取一下锁,如果获取到了锁,立即返回true,否则立即返回false。

    3.tryLock(long timeout,TimeUnit unit)

    如果获取到锁,立即返回true,否则会等待参数给定的时间单元,在等待的过程中,如果获取到了锁,就返回true,如果等待超时则返回false。

    4.lockInterruptibly()

    如果获取了锁,立即返回;如果没有获取到锁,当前线程处于休眠状态,直到获取到锁,或者当前线程被别的线程中断(会受到InterruptedException异常)。它与lock()方法最大的区别在于如果lock()方法获取不到锁就会一直处于阻塞状态,而且还会忽略interrupt()方法。

    示例如下:

    package javatest;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockTest {
    
    	public static void main(String[] args)throws InterruptedException {
    		// TODO Auto-generated method stub
    		final Lock lock=new ReentrantLock();
    		lock.lock();
    		Thread t1=new Thread(new Runnable() {
    			public void run() {
    				try {
    					lock.lockInterruptibly();
    				}catch(InterruptedException e)
    				{
    					System.out.println("interrupted");
    				}
    			}
    		});
    		t1.start();
    		t1.interrupt();
    		Thread.sleep(1);
    	}
    }
    

    运行结果如下: 

    如果把lock.lockInterruptibly()替换为lock.lock(),编译器将会提示lock.lock()catch代码块无效,这是因为lock.lock()不会抛出异常,由此可见lock()方法会忽略interrupt()引发的异常。

     

    好啦,以上就是实现Java多线程同步的三种方法的相关总结,如果大家有什么更具体的发现或者发现文中有描述错误的地方,欢迎留言评论,我们一起学习呀~~

     

    Biu~~~~~~~~~~~~~~~~~~~~宫å´éªé¾ç«è¡¨æå|é¾ç«gifå¾è¡¨æåä¸è½½å¾ç~~~~~~~~~~~~~~~~~~~~~~pia!

    展开全文
  • C++实现多线程及其三种方法实现多线程同步

    万次阅读 多人点赞 2017-06-18 21:24:18
    1.调用windows API实现多线程 #include "stdafx.h" #include #include #include DWORD WINAPI myfun1(LPVOID lpParameter); //声明线程函数 DWORD WINAPI myfun2(LPVOID lpParameter); using namespace std; ...

    1.调用windows API实现多线程

    #include "stdafx.h"
    #include <windows.h>
    #include <stdio.h>
    #include <iostream>
    
    DWORD WINAPI myfun1(LPVOID lpParameter); //声明线程函数
    DWORD WINAPI myfun2(LPVOID lpParameter);
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HANDLE h1,h2;//声明句柄变量
    	h1=CreateThread(NULL,0,myfun1,NULL,0,NULL);//创建线程1
    	cout<<"线程1开始运行!\n"<<endl;
    	h2=CreateThread(NULL,0,myfun2,NULL,0,NULL);//创建线程2
    	cout<<"线程2开始运行!"<<endl;
    	
    	//关闭线程句柄对象
    	CloseHandle(h1);
    	CloseHandle(h2);
    
    	int n=0;
    	while (1)
    	{
    		//cout<<"测试是不是循环重复执行"<<n++<<endl;
    		if (getchar()=='q')//如果用户输入字符q
    		{
    			return 0;//程序正常退出
    		}
    		else
    		{
    			Sleep(100);//程序睡眠
    		}
    	}
    
    }
    
    //分别实现线程函数,并返回值
    DWORD WINAPI myfun1(LPVOID lpParameter)
    {
    	cout<<"线程1正在运行"<<endl;
    	return 0;
    }
    
    DWORD WINAPI myfun2(LPVOID lpParameter)
    {
    	cout<<"线程2正在运行"<<endl;
    	return 0;
    }

    2.使用临界区对象、事件对象和互斥对象三种方法实现多线程同步

    #include "stdafx.h"
    #include <windows.h>
    #include <iostream>
    
    
    using namespace std;
    
    DWORD WINAPI myfun1(LPVOID lpParameter); //声明线程函数
    DWORD WINAPI myfun2(LPVOID lpParameter);
    
    /************使用临界区对象实现多线程同步***************
    临界区被初始化后,当程序进入临界区后便拥有临界区的所有权,其余线程无权进入只能等对方释放临界区之后,方可进入临界区拥有其所有权再对临界区进行操作
    InitializeCriticalSection()初始化临界区;
    EnterCriticalSection()进入临界区;
    LeaveCriticalSection()释放临界区所有权并离开临界区;
    注意:上述是windows API中相关函数,CCriticalSection类是MFC中定义的临界区类,需要在MFC程序中使用(此程序为控制台程序无法使用MFC类),可以操作临界区,lock锁定临界区、unlock释放临界区
    	  临界区为依次访问,不能实现其中一个线程一释放临界区就会被另一个线程访问临界区!不能实现实时监听
    ********************************************************/
    
    /************使用事件对象实现多线程同步***************
    事件对象是指用户在程序中使用内核对象的有无信号状态实现线程的同步临界区被初始化后,当程序进入临界区后便拥有临界区的所有权,其余线程无权进入只能等对方释放临界区之后,方可进入临界区拥有其所有权再对临界区进行操作
    CreatEvent()创建并返回事件对象;
    SetEvent()将指定的事件设置为有信号状态(有信号状态下其余线程可以访问);
    ResetEvent()将指定的事件设置为无信号状态;
    除SetEvent函数外,WaitForSingleObject函数等待指定事件。
    注意:上述是Windows API函数,CEvent类是MFC实现事件对象的类
    	  事件对象为立即访问,一旦事件对象被设置为有信号 立刻会被其余线程访问!能实现实时监听
    ********************************************************/
    
    /************使用互斥对象实现多线程同步***************
    互斥对象还可以在进程间使用,在实现线程同步时包含一个线程ID和一个计数器,线程ID表示拥有互斥对象的线程,计数器表示该互斥对象被同一线程所使用次数
    CreatMutex()创建并返回互斥对象;
    ReleaseMutex()释放互斥对象句柄;
    WaitForSingleObject()对该对象进行请求。
    注意:上述是Windows API函数,CMutex类是MFC中的互斥对象类
    	  互斥对象为立即访问,一旦互斥对象被释放 立刻会被其它正在等待的线程访问!能实现实时监听
    ********************************************************/
    
    static int a=0;
    CRITICAL_SECTION section;//定义临界区对象(Windows API)
    HANDLE hevent;//定义全局事件变量句柄
    HANDLE hmutex;//定义全局互斥对象句柄
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HANDLE h1,h2;//声明句柄变量
    	//InitializeCriticalSection(§ion);//初始化临界区对象(需要在线程前初始化)
    
    	//hevent=CreateEvent(NULL,FALSE,false,NULL);//创建事件并返回句柄
    	//SetEvent(hevent);//设置事件为有信号状态
    
    	hmutex=CreateMutex(NULL,false,NULL);//创建互斥对象并返回其句柄
    
    	h1=CreateThread(NULL,0,myfun1,NULL,0,NULL);//创建线程1
    	cout<<"线程1开始运行!\n"<<endl;
    	h2=CreateThread(NULL,0,myfun2,NULL,0,NULL);//创建线程2
    	cout<<"线程2开始运行!"<<endl;
    
    	//关闭线程句柄对象
    	CloseHandle(h1);
    	CloseHandle(h2);
    
    	while (1)
    	{
    
    		if (getchar()=='q')//如果用户输入字符q
    		{
    			//DeleteCriticalSection(§ion);//删除临界区对象
    			return 0;//程序正常退出
    		}
    		else
    		{
    			Sleep(100);//程序睡眠
    		}
    	}
    	return 0;
    
    }
    
    //分别实现线程函数,并返回值
    DWORD WINAPI myfun1(LPVOID lpParameter)
    {
    	while (true)
    	{
    		//EnterCriticalSection(§ion);//进入临界区
    
    		//WaitForSingleObject(hevent,INFINITE);//请求事件对象
    		//ResetEvent(hevent);//设置事件对象为无信号状态
    
    		WaitForSingleObject(hmutex,INFINITE);//请求互斥对象
    
    		a++;
    		if (a<1000)
    		{
    			cout<<"线程1正在计数"<<a<<endl;
    			//LeaveCriticalSection(§ion);//离开临界区
    
    			//SetEvent(hevent);
    
    			ReleaseMutex(hmutex);//释放互斥对象句柄
    		}
    		else
    		{
    			//a=0;
    			//LeaveCriticalSection(§ion);
    
    			//SetEvent(hevent);
    
    			ReleaseMutex(hmutex);//释放互斥对象句柄
    			break;
    		}
    
    	}
    
    	return 0;
    }
    
    DWORD WINAPI myfun2(LPVOID lpParameter)
    {
    //	a=0;
    	while (true)
    	{
    		//EnterCriticalSection(§ion);//进入临界区
    
    		//WaitForSingleObject(hevent,INFINITE);//请求事件对象
    		//ResetEvent(hevent);//设置事件对象为无信号状态
    
    		WaitForSingleObject(hmutex,INFINITE);//请求互斥对象
    
    		a++;
    		if (a<1000)
    		{
    			cout<<"线程2正在计数"<<a<<endl;
    			//LeaveCriticalSection(§ion);//离开临界区
    
    			//SetEvent(hevent);
    
    			ReleaseMutex(hmutex);//释放互斥对象句柄
    		}
    		else
    		{
    			//LeaveCriticalSection(§ion);//离开临界区,当计数a大于1000时退出循环
    
    			//SetEvent(hevent);
    
    			ReleaseMutex(hmutex);//释放互斥对象句柄
    			break;
    		}
    
    	}
    
    	return 0;
    }


    展开全文
  • 背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?通过多线程模拟多窗口售票为例:#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #...

    背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?

    通过多线程模拟多窗口售票为例:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    
    using namespace std;
    
    int ticket_sum=20;
    void *sell_ticket(void *arg)
    {
        for(int i=0; i<20; i++)
        {
            if(ticket_sum>0)
            {
                sleep(1);
                cout<<"sell the "<<20-ticket_sum+1<<"th"<<endl;
                ticket_sum--;
            }
        }
        return 0;
    }
    
    int main()
    {
        int flag;
        pthread_t tids[4];
    
        for(int i=0; i<4; i++)
        {
            flag=pthread_create(&tids[i],NULL,&sell_ticket,NULL);
            if(flag)
            {
                cout<<"pthread create error ,flag="<<flag<<endl;
                return flag;
            }
        }
    
        sleep(20);
        void *ans;
        for(int i=0; i<4; i++)
        {
            flag=pthread_join(tids[i],&ans);
            if(flag)
            {
                cout<<"tid="<<tids[i]<<"join erro flag="<<flag<<endl;
                return flag;
            }
            cout<<"ans="<<ans<<endl;
        }
        return 0;
    }

    image_thumb4

    分析:总票数只有20张,却卖出了23张,是非常明显的超买超卖问题,而造成这个问题的根本原因就是同时发生的各个线程都可以对ticket_sum进行读取和写入!

    ps:

    1.在并发情况下,指令执行的先后顺序由内核决定,同一个线程内部,指令按照先后顺序执行,但不同线程之间的指令很难说清楚是哪一个先执行,如果运行的结果依赖于不同线程执行的先后的话,那么就会形成竞争条件,在这样的情况下,计算的结果很难预知,所以应该尽量避免竞争条件的形成

    2.最常见的解决竞争条件的方法是将原先分离的两个指令构成一个不可分割的原子操作,而其他任务不能插入到原子操作中!

    3.对多线程来说,同步指的是在一定时间内只允许某一个线程访问某个资源,而在此时间内,不允许其他线程访问该资源!

    4.线程同步的常见方法:互斥锁,条件变量,读写锁,信号量

    一.互斥锁

    本质就是一个特殊的全局变量,拥有lock和unlock两种状态,unlock的互斥锁可以由某个线程获得,一旦获得,这个互斥锁会锁上变成lock状态,此后只有该线程由权力打开该锁,其他线程想要获得互斥锁,必须得到互斥锁再次被打开之后

    采用互斥锁来同步资源:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    
    using namespace std;
    
    int ticket_sum=20;
    pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;//static init mutex
    
    void *sell_ticket(void *arg)
    {
        for(int i=0; i<20; i++)
        {
            pthread_mutex_lock(&mutex_x);//atomic opreation through mutex lock
            if(ticket_sum>0)
            {
                sleep(1);
                cout<<"sell the "<<20-ticket_sum+1<<"th"<<endl;
                ticket_sum--;
            }
            pthread_mutex_unlock(&mutex_x);
        }
        return 0;
    }
    
    int main()
    {
        int flag;
        pthread_t tids[4];
    
        for(int i=0; i<4; i++)
        {
            flag=pthread_create(&tids[i],NULL,&sell_ticket,NULL);
            if(flag)
            {
                cout<<"pthread create error ,flag="<<flag<<endl;
                return flag;
            }
        }
    
        sleep(20);
        void *ans;
        for(int i=0; i<4; i++)
        {
            flag=pthread_join(tids[i],&ans);
            if(flag)
            {
                cout<<"tid="<<tids[i]<<"join erro flag="<<flag<<endl;
                return flag;
            }
            cout<<"ans="<<ans<<endl;
        }
        return 0;
    }

    image_thumb8

    分析:通过为售票的核心代码段加互斥锁使得其变成了一个原子性操作!不会被其他线程影响


    1.互斥锁的初始化

    互斥锁的初始化分为静态初始化和动态初始化

    静态:pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;//static init mutex

    动态:pthread_mutex_init函数

    ps:互斥锁静态初始化和动态初始化的区别?

    待补充。。。。

    2.互斥锁的相关属性及分类

    //初始化互斥锁属性
    pthread_mutexattr_init(pthread_mutexattr_t attr);
    
    //销毁互斥锁属性
    pthread_mutexattr_destroy(pthread_mutexattr_t attr);
    
    //用于获取互斥锁属性
    int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr , int *restrict pshared);
    
    //用于设置互斥锁属性
    int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr , int pshared);

    attr表示互斥锁的属性

    pshared表示互斥锁的共享属性,由两种取值:

    1)PTHREAD_PROCESS_PRIVATE:锁只能用于一个进程内部的两个线程进行互斥(默认情况)

    2)PTHREAD_PROCESS_SHARED:锁可用于两个不同进程中的线程进行互斥,使用时还需要在进程共享内存中分配互斥锁,然后为该互斥锁指定属性就可以了


    互斥锁的分类:

    //获取互斥锁类型
    int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr , int *restrict type);
    
    //设置互斥锁类型
    int pthread_mutexattr_settype(const pthread_mutexattr_t *restrict attr , int type);

    参数type表示互斥锁的类型,总共有以下四种类型:

    1.PTHREAD_MUTEX_NOMAL:标准互斥锁,第一次上锁成功,第二次上锁会失败并阻塞

    2.PTHREAD_MUTEX_RECURSIVE:递归互斥锁,第一次上锁成功,第二次上锁还是会成功,可以理解为内部有一个计数器,每加一次锁计数器加1,解锁减1

    3.PTHREAD_MUTEX_ERRORCHECK:检查互斥锁,第一次上锁会成功,第二次上锁出错返回错误信息,不会阻塞

    4.PTHREAD_MUTEX_DEFAULT:默认互斥锁,第一次上锁会成功,第二次上锁会失败

    3,测试加锁函数

    int pthread_mutex_lock(&mutex):测试加锁函数在锁已经被占据时返回EBUSY而不是挂起等待,当然,如果锁没有被占领的话可以获得锁

    为了清楚的看到两个线程争用资源的情况,我们使得其中一个函数使用测试加锁函数进行加锁,而另外一个使用正常的加锁函数进行加锁

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    int ticket_sum=20;
    pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;//static init mutex
    
    void *sell_ticket_1(void *arg)
    {
        for(int i=0; i<20; i++)
        {
            pthread_mutex_lock(&mutex_x);
            if(ticket_sum>0)
            {
                sleep(1);
                cout<<"thread_1 sell the "<<20-ticket_sum+1<<"th ticket"<<endl;
                ticket_sum--;
            }
            sleep(1);
            pthread_mutex_unlock(&mutex_x);
            sleep(1);
        }
        return 0;
    }
    
    void *sell_ticket_2(void *arg)
    {
        int flag;
        for(int i=0; i<10; i++)
        {
            flag=pthread_mutex_trylock(&mutex_x);
            if(flag==EBUSY)
            {
                cout<<"sell_ticket_2:the variable is locked by sell_ticket_1"<<endl;
            }
            else if(flag==0)
            {
                if(ticket_sum>0)
                {
                    sleep(1);
                    cout<<"thread_2 sell the "<<20-ticket_sum+1<<"th tickets"<<endl;
                    ticket_sum--;
                }
                pthread_mutex_unlock(&mutex_x);
            }
            sleep(1);
        }
        return 0;
    }
    int main()
    {
        int flag;
        pthread_t tids[2];
    
        flag=pthread_create(&tids[0],NULL,&sell_ticket_1,NULL);
        if(flag)
        {
            cout<<"pthread create error ,flag="<<flag<<endl;
            return flag;
        }
    
        flag=pthread_create(&tids[1],NULL,&sell_ticket_2,NULL);
        if(flag)
        {
            cout<<"pthread create error ,flag="<<flag<<endl;
    
            return flag;
        }
    
        void *ans;
        sleep(30);
        flag=pthread_join(tids[0],&ans);
        if(flag)
        {
            cout<<"tid="<<tids[0]<<"join erro flag="<<flag<<endl;
            return flag;
        }
        else
        {
            cout<<"ans="<<ans<<endl;
        }
    
        flag=pthread_join(tids[1],&ans);
        if(flag)
        {
            cout<<"tid="<<tids[1]<<"join erro flag="<<flag<<endl;
            return flag;
        }
        else
        {
            cout<<"ans="<<ans<<endl;
        }
    
        return 0;
    }

    QQ20190714150711_thumb1

    分析:通过测试加锁函数我们可以清晰的看到两个线程争用资源的情况


    二.条件变量

    互斥量不是万能的,比如某个线程正在等待共享数据内某个条件出现,可可能需要重复对数据对象加锁和解锁(轮询),但是这样轮询非常耗费时间和资源,而且效率非常低,所以互斥锁不太适合这种情况

    我们需要这样一种方法:当线程在等待满足某些条件时使线程进入睡眠状态,一旦条件满足,就换线因等待满足特定条件而睡眠的线程

    如果我们能够实现这样一种方法,程序的效率无疑会大大提高,而这种方法正是条件变量!

    样例:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    pthread_cond_t qready=PTHREAD_COND_INITIALIZER;   //cond
    pthread_mutex_t qlock=PTHREAD_MUTEX_INITIALIZER;  //mutex
    
    int x=10,y=20;
    
    void *f1(void *arg)
    {
      cout<<"f1 start"<<endl;
      pthread_mutex_lock(&qlock);
      while(x<y)
      {
        pthread_cond_wait(&qready,&qlock);
      }
      pthread_mutex_unlock(&qlock);
      sleep(3);
      cout<<"f1 end"<<endl;
      return 0;
    }
    
    void *f2(void *arg)
    {
      cout<<"f2 start"<<endl;
      pthread_mutex_lock(&qlock);
      x=20;
      y=10;
      cout<<"has a change,x="<<x<<" y="<<y<<endl;
      pthread_mutex_unlock(&qlock);
      if(x>y)
      {
        pthread_cond_signal(&qready);
      }
      cout<<"f2 end"<<endl;
      return 0;
    }
    
    int main()
    {
      pthread_t tids[2];
      int flag;
    
      flag=pthread_create(&tids[0],NULL,f1,NULL);
      if(flag)
      {
        cout<<"pthread 1 create error "<<endl;
        return flag;
      }
    
      sleep(2);
    
      flag=pthread_create(&tids[1],NULL,f2,NULL);
      if(flag)
      {
        cout<<"pthread 2 create erro "<<endl;
        return flag;
      }
    
      sleep(5);
      return 0;
    }

    QQ20190714155203_thumb1

    分析:线程1不满足条件被阻塞,然后线程2运行,改变了条件,线程2发行条件改变了通知线程1运行,然后线程2结束,然后线程1继续运行,然后线程1结束,为了确保线程1先执行,在创建线程2之前我们sleep了2秒

    ps:

    1.条件变量通过运行线程阻塞和等待另一个线程发送信号的方法弥补互斥锁的不足,常常和互斥锁一起使用,使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开响应的互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知响应的条件变量换线一个或多个正被此条件变量阻塞的线程,这些线程将重新锁定互斥锁并且重新测试条件是否满足

    1.条件变量的相关函数

    1)创建

    静态方式:pthread_cond_t cond PTHREAD_COND_INITIALIZER

    动态方式:int pthread_cond_init(&cond,NULL)

    Linux thread 实现的条件变量不支持属性,所以NULL(cond_attr参数)

    2)注销

    int pthread_cond_destory(&cond)

    只有没有线程在该条件变量上,该条件变量才能注销,否则返回EBUSY

    因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程!(请参考条件变量的底层实现)

    3)等待

    条件等待:int pthread_cond_wait(&cond,&mutex)

    计时等待:int pthread_cond_timewait(&cond,&mutex,time)

    1.其中计时等待如果在给定时刻前条件没有被满足,则返回ETIMEOUT,结束等待

    2.无论那种等待方式,都必须有一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait形成竞争条件!

    3.在调用pthread_cond_wait前必须由本线程加锁

    4)激发

    激发一个等待线程:pthread_cond_signal(&cond)

    激发所有等待线程:pthread_cond_broadcast(&cond)

    重要的是,pthread_cond_signal不会存在惊群效应,也就是是它最多给一个等待线程发信号,不会给所有线程发信号唤醒提他们,然后要求他们自己去争抢资源!

    pthread_cond_signal会根据等待线程的优先级和等待时间来确定激发哪一个等待线程


    下面看一个程序,找到程序存在的问题

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    pthread_cond_t taxi_cond=PTHREAD_COND_INITIALIZER; //taix arrive cond
    pthread_mutex_t taxi_mutex=PTHREAD_MUTEX_INITIALIZER;// sync mutex
    
    void *traveler_arrive(void *name)
    {
        cout<<"Traveler:"<<(char*)name<<" needs a taxi now!"<<endl;
        pthread_mutex_lock(&taxi_mutex);
        pthread_cond_wait(&taxi_cond,&taxi_mutex);
        pthread_mutex_unlock(&taxi_mutex);
        cout<<"Traveler:"<<(char*)name<<" now got a taxi!"<<endl;
        pthread_exit((void*)0);
    }
    
    void *taxi_arrive(void *name)
    {
        cout<<"Taxi:"<<(char*)name<<" arriver."<<endl;
        pthread_cond_signal(&taxi_cond);
        pthread_exit((void*)0);
    }
    
    int main()
    {
        pthread_t tids[3];
        int flag;
    
        flag=pthread_create(&tids[0],NULL,taxi_arrive,(void*)("Jack"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        flag=pthread_create(&tids[1],NULL,traveler_arrive,(void*)("Susan"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        flag=pthread_create(&tids[2],NULL,taxi_arrive,(void*)("Mike"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        void *ans;
        for(int i=0; i<3; i++)
        {
            flag=pthread_join(tids[i],&ans);
            if(flag)
            {
                cout<<"pthread_join error:flag="<<flag<<endl;
                return flag;
            }
            cout<<"ans="<<ans<<endl;
        }
        return 0;
    }

    QQ20190715105012_thumb1

    分析:程序由一个条件变量,用于提示乘客有出租车到达,还有一个同步锁,乘客到达之后就是等车(条件变量),出租车到达之后就是通知乘客,我们看到乘客Susan到达之后,并没有乘坐先到的Jack的车,而是等到Mike的车到了之后再乘坐Mike的车,Jack的车白白的闲置了,为什么会造成这种原因呢?分析一下代码:我们发现Jack出租车到达之后调用pthread_cond_signal(&taxi_cond)发现没有乘客,然后就直接结束线程了。。。。

    正确的操作应该是:先到的Jack发现没有乘客,然后一直等待乘客,有乘客到了就直接走,而且我们应该统计一下乘客的数量

    做如下改进:

    1.增加乘客计数器,使得出租车在有乘客到达之后可以直接走,而不是又在原地等待别的乘客(僵死线程)

    2.出租车到达函数加个while循环,没有乘客的时候一直等待,直到乘客到来

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    pthread_cond_t taxi_cond=PTHREAD_COND_INITIALIZER; //taix arrive cond
    pthread_mutex_t taxi_mutex=PTHREAD_MUTEX_INITIALIZER;// sync mutex
    
    
    void *traveler_arrive(void *name)
    {
        cout<<"Traveler:"<<(char*)name<<" needs a taxi now!"<<endl;
        pthread_mutex_lock(&taxi_mutex);
     
        pthread_cond_wait(&taxi_cond,&taxi_mutex);
        pthread_mutex_unlock(&taxi_mutex);
        cout<<"Traveler:"<<(char*)name<<" now got a taxi!"<<endl;
        pthread_exit((void*)0);
    }
    
    void *taxi_arrive(void *name)
    {
        cout<<"Taxi:"<<(char*)name<<" arriver."<<endl;
        
        pthread_exit((void*)0);
    }
    
    int main()
    {
        pthread_t tids[3];
        int flag;
    
        flag=pthread_create(&tids[0],NULL,taxi_arrive,(void*)("Jack"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        flag=pthread_create(&tids[1],NULL,traveler_arrive,(void*)("Susan"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        flag=pthread_create(&tids[2],NULL,taxi_arrive,(void*)("Mike"));
        if(flag)
        {
            cout<<"pthread_create error:flag="<<flag<<endl;
            return flag;
        }
        cout<<"time passing by"<<endl;
        sleep(1);
    
        void *ans;
        for(int i=0; i<3; i++)
        {
            flag=pthread_join(tids[i],&ans);
            if(flag)
            {
                cout<<"pthread_join error:flag="<<flag<<endl;
                return flag;
            }
            cout<<"ans="<<ans<<endl;
        }
        return 0;
    }

    三.读写锁

    可以多个线程同时读,但是不能多个线程同时写

    1.读写锁比互斥锁更加具有适用性和并行性

    2.读写锁最适用于对数据结构的读操作读操作次数多余写操作次数的场合!

    3.锁处于读模式时可以线程共享,而锁处于写模式时只能独占,所以读写锁又叫做共享-独占锁

    4.读写锁有两种策略:强读同步和强写同步

    在强读同步中,总是给读者更高的优先权,只要写者没有进行写操作,读者就可以获得访问权限

    在强写同步中,总是给写者更高的优先权,读者只能等到所有正在等待或者执行的写者完成后才能进行读

    不同的系统采用不同的策略,比如航班订票系统使用强写同步,图书馆查阅系统采用强读同步

    根据不同的业务场景,采用不同的策略

    1)初始化的销毁读写锁

    静态初始化:pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER

    动态初始化:int pthread_rwlock_init(rwlock,NULL),NULL代表读写锁采用默认属性

    销毁读写锁:int pthread_rwlock_destory(rwlock)

    在释放某个读写锁的资源之前,需要先通过pthread_rwlock_destory函数对读写锁进行清理。释放由pthread_rwlock_init函数分配的资源

    如果你想要读写锁使用非默认属性,则attr不能为NULL,得给attr赋值

    int pthread_rwlockattr_init(attr),给attr初始化

    int pthread_rwlockattr_destory(attr),销毁attr

    2)以写的方式获取锁,以读的方式获取锁,释放读写锁

    int pthread_rwlock_rdlock(rwlock),以读的方式获取锁

    int pthread_rwlock_wrlock(rwlock),以写的方式获取锁

    int pthread_rwlock_unlock(rwlock),释放锁

    上面两个获取锁的方式都是阻塞的函数,也就是说获取不到锁的话,调用线程不是立即返回,而是阻塞执行,在需要进行写操作的时候,这种阻塞式获取锁的方式是非常不好的,你想一下,我需要进行写操作,不但没有获取到锁,我还一直在这里等待,大大拖累效率

    所以我们应该采用非阻塞的方式获取锁:

    int pthread_rwlock_tryrdlock(rwlock)

    int pthread_rwlock_trywrlock(rwlock)


    读写锁的样例:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    using namespace std;
    
    int num=5;
    pthread_rwlock_t rwlock;
    
    void *reader(void *arg)
    {
      pthread_rwlock_rdlock(&rwlock);
      cout<<"reader "<<(long)arg<<" got the lock"<<endl;
      pthread_rwlock_unlock(&rwlock);
      return 0;
    }
    
    void *writer(void *arg)
    {
      pthread_rwlock_wrlock(&rwlock);
      cout<<"writer "<<(long)arg<<" got the lock"<<endl;
      pthread_rwlock_unlock(&rwlock);
      return 0;
    }
    
    int main()
    {
      int flag;
      long n=1,m=1;
      pthread_t wid,rid;
      pthread_attr_t attr;
    
      flag=pthread_rwlock_init(&rwlock,NULL);
      if(flag)
      {
        cout<<"rwlock init error"<<endl;
        return flag;
      }
    
      pthread_attr_init(&attr);
      pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//thread sepatate
    
      for(int i=0;i<num;i++)
      {
        if(i%3)
        {
          pthread_create(&rid,&attr,reader,(void *)n);
          cout<<"create reader "<<n<<endl;
          n++;
        }else
        {
          pthread_create(&wid,&attr,writer,(void *)m);
          cout<<"create writer "<<m<<endl;
          m++;
        }
      }
    
      sleep(5);//wait other done
      return 0;
    }

    QQ20190715152536_thumb2

    分析:3个读线程,2个写线程,读线程比写线程多

    当读写锁是写状态时,在锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞

    当读写锁是读状态时,在锁被解锁之前,所有视图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行加锁的线程会被阻塞

    所以读写锁默认是强读模式!


    四.信号量

    信号量(sem)和互斥锁的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程进入临界区

    1)信号量初始化

    int sem_init(&sem,pshared,v)

    pshared为0表示这个信号量是当前进程的局部信号量

    pshared为1表示这个信号量可以在多个进程之间共享

    v为信号量的初始值

    成功返回0,失败返回-1

    2)信号量值的加减

    int sem_wait(&sem):以原子操作的方式将信号量的值减去1

    int sem_post(&sem):以原子操作的方式将信号量的值加上1

    3)对信号量进行清理

    int sem_destory(&sem)


    通过信号量模拟2个窗口,10个客人进行服务的过程

    样例:

    #include <iostream>
    #include<pthread.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<unistd.h>
    #include<errno.h>
    #include<semaphore.h>
    using namespace std;
    
    
    int num=10;
    sem_t sem;
    
    void *get_service(void *cid)
    {
      int id=*((int*)cid);
      if(sem_wait(&sem)==0)
      {
         sleep(5);
         cout<<"customer "<<id<<" get the service"<<endl;
         cout<<"customer "<<id<<" done "<<endl;
         sem_post(&sem);
      }
      return 0;
    }
    
    int main()
    {
      sem_init(&sem,0,2);
      pthread_t customer[num];
      int flag;
    
      for(int i=0;i<num;i++)
      {
        int id=i;
        flag=pthread_create(&customer[i],NULL,get_service,&id);
        if(flag)
        {
          cout<<"pthread create error"<<endl;
          return flag;
        }else
        {
          cout<<"customer "<<i<<" arrived "<<endl;
        }
        sleep(1);
      }
    
      //wait all thread done
      for(int j=0;j<num;j++)
      {
        pthread_join(customer[j],NULL);
      }
      sem_destroy(&sem);
      return 0;
    }

    QQ20190715165657_thumb2

    分析:信号量的值代表空闲的服务窗口,每个窗口一次只能服务一个人,有空闲窗口,开始服务前,信号量-1,服务完成后信号量+1


    总结完毕:Linux c++线程同步的四种方式:互斥锁,条件变量,读写锁,信号量



    展开全文
  • 多线程08线程安全静态同步方法

    千次阅读 2022-02-08 20:53:01
    package demo08线程安全静态同步方法; //实现买票案例 //卖票出现了线程安全问题:卖出了不存在的票和重复的票 //使用线程安全的一种方案:使用同步方法 /* 使用步骤: 1.把访问的共享数据的代码块抽取出来,放到一...
  • Android面试之线程同步方法

    千次阅读 2021-12-06 11:39:58
    一 什么是进程和线程?进程和线程的区别? 进程是资源分配的最小单元,线程是...而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很,同时创建一个线程的开销也比进程要小很
  • python的多线程线程同步方式

    千次阅读 2019-07-17 09:37:23
    1.线程执行 join与setDaemon 1.子线程在主线程运行结束后,会继续执行完,如果给子线程设置为守护线程(setDaemon=True),主线程运行结束子线程即结束; 2 .如果join()线程,那么主线程会等待子线程执行完再执行...
  • java多线程同步5种方法

    万次阅读 多人点赞 2018-05-28 22:11:07
    二、为什么要线程同步因为当我们有个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作...
  • 多线程实现同步的七种方式

    千次阅读 2017-11-15 19:43:55
    JAVA中线程同步的方法(7种)汇总 同步的方法: 一、同步方法  即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前...
  • 生产者与消费者问题是多线程中非常经典的线程同步问题,这些线程必须按照一定的生产率和消费率来访问共享缓冲区。实现方法是:设置两个信号量full和empty,其中full表示消费缓冲区的个数,empty表示生产缓冲区的个数...
  • Android-线程常用方法-线程同步

    千次阅读 2019-02-16 15:45:52
    线程常用方法: 1.start():线程调用该方法将启动线程从新建状态进入就绪,一旦轮到享用CPU资源时,就开始自己的生命周期 2.run():Thread类的run()方法与Runnable接口的run()方法的功能和作用相同,都用来定义线程对象...
  • linux中实现线程同步的6种方法

    万次阅读 多人点赞 2020-10-22 16:37:21
    linux线程同步方法 下面是一个线程不安全的例子: #include<stdio.h> #include<pthread.h> int ticket_num=10000000; void *sell_ticket(void *arg) { while(ticket_num>0) { ticket_num--; }...
  • C#多线程同步的几种方法

    千次阅读 2017-03-06 14:37:07
    多线程操作的时候我们知道要避免线程之间共享数据,但是很多时候我们要使用多线程并且还要访问同一块内存的数据,这是我们就必须要使用同步技术,确保一次只有一个线程访问和改变共享状态。 下面我就来说一下同步...
  • 【Java多线程】synchronized同步方法

    万次阅读 2021-10-10 23:54:05
    “非线程安全”其实会在线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取得的数据其实是被更改过的。...两个线程访问同一个对象中的同步方法时一定是线程安全的
  • 最近小扁我被问到 实现线程同步有哪几种方法,而我只知道使用同步关键字synchronized来实现而已(⊙o⊙),,所以有必要来学习一下实现线程同步的几种方法;各位看官,若有非议(不接受反驳),请不吝赐教! 实现...
  • C++多线程并发(二)---线程同步之互斥锁

    万次阅读 多人点赞 2019-03-20 00:08:29
    一、何为线程同步 在前一篇文章《C++多线程并发编程(一)—线程管理》中解释多线程并发时说到两个比较重要的概念: 多线程并发:在同一时间段内交替处理多个操作,线程切换时间片是很短的(一般为毫秒级),一个...
  • Qt多线程同步

    千次阅读 2018-09-15 12:06:04
    一、Qt中使用多线程时候,多线程同步就是一个不可避免的问题。多线程同步就是使多个线程在同时执行同一段代码的时候,有顺序的执行,不会出现同时有两个或者多个线程执行同一段代码的情况,特别是在对变量或者...
  • C++11 多线程同步

    千次阅读 2016-11-09 21:28:05
    出现数据竞争,一般会用临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)这四种方法来完成线程同步。 1、临界区 对于临界资源,多线程必须互斥地对它进行访问。每个...
  • Java学习笔记---多线程同步的五种方法

    万次阅读 多人点赞 2015-09-09 18:33:49
    二、为什么要线程同步 因为当我们有个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程...
  • 一、多线程同步关键字-synchronized1.概念 synchronized保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。当多个并发线程访问同一个对象object中的同步...
  • Java中多线程多线程的实现方式、同步代码块的方式
  • 多线程之间如何实现同步

    万次阅读 2019-07-01 11:56:53
    线程安全问题一般是发生再多线程环境,当多个线程同时共享一个全局变量或静态变量做写的操作时候,可能会发生数据冲突问题,也就是线程安全问题,在读的操作不会发生数据冲突问题 下面看个简单的买票例子 案例:需求...
  • Linux线程同步的三种方法

    千次阅读 2016-04-29 11:40:25
    Linux 线程同步的三种方法线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。 一、互斥锁(mutex) 通过锁机制...
  • Java多线程同步优化的6种方案

    万次阅读 2020-06-24 17:44:34
    Java中可以使用锁来解决多线程的同步问题,保障了数据的一致性,但也会代理很多问题,本章总结了多线程同步的几种优化方案:包括读写锁、写时复制机制、锁细化等方案。
  • 线程同步(7种同步方法

    万次阅读 2018-09-20 18:27:10
    java允许多线程并发控制,当多线程同时操作一个可共享的资源变量时,将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程调用,从而保证了该变量的唯一性和准确性 ...
  • Java 多线程同步和异步详解

    千次阅读 2018-05-31 10:00:32
    转载自 https://www.cnblogs.com/mengyuxin/p/5358364.htmljava线程 同步与异步 线程池1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了...
  • 多线程间的通信和同步

    千次阅读 多人点赞 2019-06-15 11:21:07
    最近看了很多关于网络编程和多线程的书,为了以后查看相关内容方便,整理了几本书的精华形成这篇博文,希望能帮助观看这篇博文的读者。 目录 一、什么是多线程? 二、为什么要创建线程 三、线程之间如何通信 四...
  • java多线程同步的五种方法

    千次阅读 2017-09-12 10:15:13
    因为当我们有线程要同时访问同一个变量或对象时,如果这些线程中午既有读又有写操作时,就会导致变量值或者对象的状态出现混乱,从而导致程序异常,举个例子:如果同一个银行账户被连个线程操作,一个存钱1000....
  •   1、确保线程互斥访问同步代码;   2、保证共享变量的修改能够及时可见;   3、有效解决指令重排序问题。   synrhronized关键字简洁、清晰、语义明确,因此即使有了Lock接口,使用的还是非常广泛。其应用层...
  • C#多线程——线程同步

    万次阅读 2018-08-25 13:11:53
    一、为什么要线程同步?...二、实现线程同步方法: • 使用Mutex类 • 使用SemaphoreSlim类 • 使用AutoResetEvent类 • 使用ManualResetEventSlim类 • 使用CountDownEvent类 • 使用Barrier类 • 使用Reade...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 858,171
精华内容 343,268
关键字:

多线程同步的方法