并发编程

目录

一、Python多线程编程

二、Python多协程编程

三、Python多进程编程

四、C++多线程编程

五、C++多协程编程

六、C++多进程编程


当前的应用程序很少是单线程的,从按钮的点击响应,到网络数据的请求,都需要用户无感知的处理,如果处理的过程不是非常迅速,那么就会对UI线程产生不良影响。

并发编程的方式主要有:多线程、多协程、多进程三种方式。多线程可以充分利用CPU资源,处理速度最快,内存共享,但是处理不当非常容易造成内存访问问题或线程不能正常退出等问题。多协程多用于处理异步IO,由于始终都是一个线程,所以不会造成内存问题,同时也不需要关注程序关闭等问题,可以很好的解决IO阻塞的情况。多进程的方式多用于稳定性高的项目,或者是多种语言编写的的程序。一个程序由多个模块组成,一个程序的异常退出只会对退出的服务产生影响,其它服务可以照常运行。同时程序异常退出后,可以由守护进程重启启动。因此可以保证程序的高度稳定性。但是多进程程序数据交换更加困难,需要使用进程间通信。

一、Python多线程编程

  • 使用线程对象

    import threading
    def mov(func):
        for i in range(0,10):
            print(i,func)
    ​
    if __name__ == '__main__':
        t1 = threading.Thread(target=mov,args=('Python多线程',))
        # 设置守护线程
        t1.setDaemon(False)
        t1.start()
        # t1.join()

     

  • 创建线程类

    import threading
    number = 0
    lock = threading.Lock()
    ​
    class Producer(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
    ​
    ​
        def run(self):
            global number, number
            lock.acquire()
            number += 1
            print(number)
            lock.release()
    ​
    ​
    ​
    class Customer(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
    ​
        def run(self):
            global number, lock
            lock.acquire()
            number -= 1
            print(number)
            lock.release()
    ​
    if __name__ == '__main__':
        t1 = Producer()
        t2 = Customer()
        t1.start()
        t2.start()
        t1.join()
        t2.join()

     

二、Python多协程编程

  • 引入:使用多线程处理网络请求

    import time
    import socket
    import threading
    import sys
    ​
    def doRequest():
        sock = socket.socket()
        sock.connect(('www.baidu.com',80))
        sock.send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n".encode("utf-8"))
        response = sock.recv(1024)
        print(response)
        return response
    ​
    if __name__ == '__main__':
        threads = []
        start = time.time()
        for i in range(0,10):
            threads.append(threading.Thread(target=doRequest))
        for i in threads:
            i.start()
        for i in threads:
            i.join()
    ​
        print('spend time:',time.time() - start)

     

  • 使用非阻塞的方式

    import time
    import socket
    import threading
    import sys
    ​
    def doRequest():
        sock = socket.socket()
        sock.setblocking(False)
        try:
            sock.connect(('www.baidu.com',80))
        except BlockingIOError:
            pass
        while True:
            try:
                sock.send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n".encode("utf-8"))
                break
            except OSError:
                pass
        while True:
            try:
                response = sock.recv(1024)
                break
            except OSError:
                pass
        return response
    ​
    if __name__ == '__main__':
        threads = []
        start = time.time()
        for i in range(0,10):
            print(doRequest())
    ​
        print('spend time:',time.time() - start)

     

  • 使用回调

    import selectors
    import socket
    ​
    class Server(object):
        def __init__(self, sel, sock):
            self.sel = sel
            self.sock = sock
    ​
        def run(self, host, port):
            self.sock.bind((host, port))
            self.sock.listen(1000)
            self.sock.setblocking(False)
            self.sel.register(sock, selectors.EVENT_READ, self.accept)
            while True:
                events = self.sel.select()  # m默认阻塞,有活动连接就返回活动连接列表
                for key, mask in events:
                    callback = key.data  # accept
                    callback(key.fileobj, mask)
    ​
        def accept(self, sock, mask):
            conn, addr = sock.accept()  # Should be ready
            print('accepted', conn, 'from', addr)
            conn.setblocking(False)
            sel.register(conn, selectors.EVENT_READ, self.read)
    ​
        def read(self, conn, mask):
            data = conn.recv(1024)  # Should be ready
            if data:
                print('echoing', repr(data), 'to', conn)
                conn.send(data)  # Hope it won't block
            else:
                print('closing', conn)
                sel.unregister(conn)
                conn.close()
    ​
    ​
    if __name__ == '__main__':
        sel = selectors.DefaultSelector()
        sock = socket.socket()
        host, port = 'localhost', 10001
        server_obj = Server(sel, sock)
        server_obj.run(host, port)
    import socket
    ​
    HOST = 'localhost'
    PORT = 10001
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
    while True:
        msg = bytes(input(">>:").strip(), encoding="utf-8")
        if msg == 'q'.encode("utf-8"):
            exit("退出!")
        s.sendall(msg)
        data = s.recv(1024)
        print('Received', data.decode())
    s.close()

    另外使用回调接收HTML数据的例子:

    import socket
    from selectors import DefaultSelector, EVENT_WRITE, EVENT_READ
    import sys
    ​
    selector = DefaultSelector()
    stopped = False
    urls_todo = []
    ​
    ​
    class Crawler:
        def __init__(self, url):
            self.url = url
            self.sock = None
            self.response = b''
    ​
        def fetch(self):
            self.sock = socket.socket()
            self.sock.setblocking(False)
            try:
                self.sock.connect(('www.baidu.com', 80))
            except BlockingIOError:
                pass
            selector.register(self.sock.fileno(), EVENT_WRITE, self.connected)
    ​
        def connected(self, key, mask):
            selector.unregister(key.fd)
            get = 'GET {0} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format(self.url)
            self.sock.send(get.encode('ascii'))
            selector.register(key.fd, EVENT_READ, self.read_response)
    ​
        def read_response(self, key, mask):
            global stopped
            # 如果响应大于4KB,下一次循环会继续读
            chunk = self.sock.recv(4096)
            if chunk:
                self.response += chunk
            else:
                selector.unregister(key.fd)
                print(self.response)
                urls_todo.remove(self.url)
                if not urls_todo:
                    stopped = True
    ​
    ​
    def loop():
        while not stopped:
            # 阻塞, 直到一个事件发生
            events = selector.select()
            for event_key, event_mask in events:
                callback = event_key.data
                callback(event_key, event_mask)
    ​
    ​
    if __name__ == '__main__':
        import time
    ​
        start = time.time()
        for i in range(10):
            urls_todo.append("/" + str(i))
            crawler = Crawler("/" + str(i))
            crawler.fetch()
        loop()
        print("spend time : %s" % (time.time() - start))

     

  • 使用生成器

    首先,需要了解yield语法

    1. yield语法类似于return,当函数中有yield时,表示当前函数是生成器,函数不能直接被调用

    2. 使用next(func),从开始(上次执行的地方)继续运行生成器

    3. 使用func.send(argv),从开始(上次执行的地方)继续运行生成器,并将传入参数作为返回值传给当前程序

    4. <<<Demo>>>使用生成器读取文件

      def read_file(fpath):
          BLOCK_SIZE = 3
          with open(fpath,'rb') as f:
              while True:
                  block=f.read(BLOCK_SIZE)
                  if block:
                      yield block
                  else:
                      return
      ​
      if __name__ == '__main__':
          f = read_file('server.py')
          for i in range(10):
              print(f.__next__())

       

    然后,使用select监听socket端口

    1. select.select示例代码

      import socket, select
      
      s = socket.socket()
      print('s:', s)
      host = socket.gethostname()
      port = 1234
      s.bind((host, port))
      
      s.listen(5)
      
      inputs = [s]
      while True:
          rs, ws, es = select.select(inputs, [], [])  # 1、select函数阻塞进程,直到inputs中的套接字被触发(在此例中,套接字接收到客户端发来的握手信号,从而变得可读,满足select函数的“可读”条件),rs返回被触发的套接字(服务器套接字);
                                                      # 4、select再次阻塞进程,同时监听服务器套接字和获得的客户端套接字;
          for r in rs:
              if r is s:                              # 2、如果是服务器套接字被触发(监听到有客户端连接服务器)
                  c, addr = s.accept()
                  print('Got connection from', addr)
                  inputs.append(c)                    # 3、inputs加入客户端套接字
              else:                                   # 5、当客户端发送数据时,客户端套接字被触发,rs返回客户端套接字,然后进行下一步处理。
                  try:
                      data = r.recv(1024)
                      disconnected = not data
                  except socket.error:
                      disconnected = True
                  if disconnected:
                      print(r.getpeername(), 'disconnected')
                      inputs.remove(r)
                  else:
                      print(data)

       

    select.select可以绑定socket套接字的读写变化,正常情况下,在select.select的时候发生阻塞,除非当读写请求发生变化时,当前代码会继续执行,然后可以判断当前的请求是connect连接成功发出的等待连接请求还是connect完成之后发出的可读请求。

    最后看一下源码

    import select
    import socket
    import time
    import sys
    ​
    num = 10
    ​
    def coroutine():
        sock = socket.socket()
        sock.setblocking(0)
        address = yield sock
        try:
            sock.connect(address)
        except BlockingIOError:
            pass
        data = yield
        size = yield sock.send(data)
        yield sock.recv(size)
    ​
    ​
    def main():
        # socket列表
        inputs = []
        # socket id
        outputs = []
        # 生成器
        coros = []
        # key:socket id,value: 生成器
        coro_dict = dict()
        for i in range(num):
            coros.append(coroutine())
            sock = coros[-1].send(None)  # 发送一个None值来启动生成器
            outputs.append(sock.fileno())  #
            # print(outputs)
            coro_dict[sock.fileno()] = coros[-1]
            coros[-1].send(('www.baidu.com', 80))
        while True:
            r_list, w_list, e_list = select.select(inputs, outputs, ())
            for i in w_list:
                # print(type(i))
                coro_dict[i].send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n')
                outputs.remove(i)
                inputs.append(i)
            for i in r_list:
                coro_dict[i].send(1024)
                inputs.remove(i)
            if len(inputs) == len(outputs) == 0:
                break
                # time.sleep(2)
        # coro.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n')
        # select.select(wait_list, (), ())
        # print(coro.send(1024))
    ​
    ​
    start = time.time()
    main()
    print("spend time : %s" % (time.time() - start))
  • 使用yield form

    1. yield from可以从生成器中产生生成器。

    2. 解释:yield from实际上是相当于对生成器的调用。因为生成器不能被直接调用,所以通过yield from相当于将生成器的代码拷贝到当前的代码段。

    3. 例如:

      def g(x):
          yield from range(10)
      ​
      func = g(10)
      print(next(func))
      l = list(func)
      print(l)
      ​

      可以看到,yield from的用法。同时可以发现list可以将生成器放置到列表中。

    4. 异步函数和异步生成器

      # 普通函数
      def function():
          return 1
      ​
      # 生成器函数
      def generator():
          yield 1
      ​
      # 异步函数
      async def async_function():
          return 1
      ​
      # 异步生成器
      async def async_generator():
          yield 1
      ​
      # 普通的生成器只需要通过send就可以驱动生成器的执行
      print(generator().send(None))
      ​
      # 异步函数也需要通过send驱动执行,只不过它的执行结果存放在StopIteration异常中
      try:
          async_function().send(None)
      except StopIteration as e:
          print(e.value)
      ​
      # 在异步函数中,可以使用await函数等待另一个异步函数执行完成
      async def await_coroutine():
          result = await async_function()
          print(result)
      ​
      # 通过执行等待异步函数,完成对异步函数的调用
      try:
          await_coroutine().send(None)
      except:
          pass
    5. asyncio

      编写异步的Hello world

      import asyncio
      ​
      @asyncio.coroutine
      def hello():
          print('Hello world')
          r = yield from asyncio.sleep(1)
          print('hello again!')
      ​
      if __name__ == '__main__':
          loop=asyncio.get_event_loop()
          loop.run_until_complete(hello())
          loop.close()

      @asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。

      hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

      把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

      使用asyncio编写异步Task

      import asyncio
      ​
      @asyncio.coroutine
      def wget(host):
          print('wget %s...',host)
          connect=asyncio.open_connection(host,80)
          reader, writer = yield from connect
          header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
          writer.write(header.encode('utf-8'))
          yield from writer.drain()
          while True:
              line = yield from reader.readline()
              if line == b'\r\n':
                  break
              print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
              # Ignore the body, close the socket
          writer.close()
      ​
      if __name__ == '__main__':
          loop = asyncio.get_event_loop()
          tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
          loop.run_until_complete(asyncio.wait(tasks))
          loop.close()
      ​

      with上下文管理器:

      with open("new.txt","w") as f:
          f.write("Hello world")

      可以保证程序关闭文件,那么如何自定义上下文管理器?

      class VOW(object):
          def __init__(self,text):
              self.text = text
      
          def __enter__(self):
              self.text = "I say: " + self.text  # add prefix
              return self  # note: return an object
      
          def __exit__(self, exc_type, exc_value, traceback):
              self.text = self.text + "!"  # add suffix
      
      with VOW("I'm fine") as myvow:
          print(myvow.text)
      
      print(myvow.text)

      async with(异步上下文管理器):

      import asyncio
      import aiohttp
      import time
      
      async def getGithub():
          async with aiohttp.ClientSession(loop=loop) as session:
              async with session.get('https://github.com') as response:
                  text = await response.read()
                  print(text)
                  return text
      
      if __name__ == '__main__':
          start = time.time()
          loop = asyncio.get_event_loop()
          tasks = [getGithub() for i in range(10)]
          loop.run_until_complete(asyncio.wait(tasks))
          # loop.run_until_complete(asyncio.gather(*tasks))
          print("spend time : %s" % (time.time() - start))

      总结:使用select+多线程实现线程安全退出

      client.py

      import socket
      import threading
      import time
      
      def socket_thread():
          sk = socket.socket()
          sk.connect(("127.0.0.1", 8888))
          print('begin connnect...')
          sk.sendall('Hello'.encode('utf-8'))
          sk.close()
      
      if __name__ == '__main__':
          t = threading.Thread(target=socket_thread)
          t.start()
          time.sleep(3)

      server.py

      import socket
      import threading
      import time
      import select
      isRunning = True
      def socket_thread():
          s = socket.socket()
          s.bind(('127.0.0.1',8888))
          s.listen(5)
          inputs = [s]
          while True:
              rs, ws, es = select.select(inputs, [], [], 0.5)
              for r in rs:
                  if r is s:
                      conn, _ = s.accept()
                      inputs.append(conn)
                  else:
                      data = r.recv(1024)
                      inputs.remove(r)
                      # conn.close()
                      print(data)
              if isRunning==False:
                  break
      
      if __name__ == '__main__':
          start = time.clock()
          t = threading.Thread(target=socket_thread)
          t.start()
          time.sleep(10)
          print('end',time.clock() - start)
          isRunning = False
          print('ended',time.clock() - start)

       

三、Python多进程编程

正常的多进程中可以使用管道,共享内存等多种方式,这里使用的第三方集成好的zmq——进程间的消息队列,这种方式可以在进程间收发不同步时仍然保证消息不丢失。

  1. PyZMQ三种模型

    • 应答模型(Request-Reply)

      server.py

      import zmq
      
      context = zmq.Context()
      socket = context.socket(zmq.REP)
      socket.bind("tcp://*:5555")
      
      while True:
          message = socket.recv()
          print(message)
          socket.send_string("server response!")

      client.py

      import zmq
      import sys
      
      context = zmq.Context()
      socket = context.socket(zmq.REQ)
      socket.connect("tcp://localhost:5555")
      
      while True:
          data = input("input your data:")
          if data == 'q':
              sys.exit()
          socket.send_string(data)
          response = socket.recv();
          print(response)
      

       

    • 发布-订阅模型(Publish-Subscribe)

      server.py

      import zmq
      if __name__ == '__main__':
          context = zmq.Context()
          socket = context.socket(zmq.PUB)
          socket.bind("tcp://127.0.0.1:5000")
          while True:
              msg = input('input your data:')
              socket.send_string(msg)

      client.py

      import time
      import zmq
      if __name__ == '__main__':
          context = zmq.Context()
          socket = context.socket(zmq.SUB)
          socket.connect("tcp://127.0.0.1:5000")
          socket.setsockopt_string(zmq.SUBSCRIBE, '')
          while True:
              print(socket.recv_string())
    • 推拉模型(Parallel Pipeline)

      server.py

      import zmq
      ​
      context = zmq.Context()
      ​
      socket = context.socket(zmq.PULL)
      socket.bind('tcp://*:5558')
      ​
      while True:
          data = socket.recv_string()
          print(data)

      work.py

      import zmq
      ​
      context = zmq.Context()
      ​
      recive = context.socket(zmq.PULL)
      recive.connect('tcp://127.0.0.1:5557')
      ​
      sender = context.socket(zmq.PUSH)
      sender.connect('tcp://127.0.0.1:5558')
      ​
      while True:
          data = recive.recv_string()
          print(data)
          sender.send_string(data)

      client.py

      import zmq
      import time
      
      context = zmq.Context()
      socket = context.socket(zmq.PUSH)
      
      socket.bind('tcp://*:5557')
      
      while True:
          data = input('input your data:')
          socket.send_string(data)

       

  2. 多线程+非阻塞模型

    使用Poller().poll可以让zmq维护自己的消息队列,类似于select模型

    server.py

    import zmq
    
    if __name__ == '__main__':
        context = zmq.Context()
        socket = context.socket(zmq.PUB)
        socket.bind("tcp://127.0.0.1:5000")
        while True:
            msg = input('input your data:')
            socket.send_string(msg)

    client.py

    import time
    import zmq
    import threading
    
    isBlocking = True
    def zmq_thread():
        context = zmq.Context()
        socket = context.socket(zmq.SUB)
        socket.connect("tcp://127.0.0.1:5000")
        socket.setsockopt_string(zmq.SUBSCRIBE, '')
        poller = zmq.Poller()
        poller.register(socket, zmq.POLLIN)
        while True:
            socks = dict(poller.poll(500))
            if socket in socks:
                message = socket.recv_string()
                print(message)
            if isBlocking==False:
                break
    
    if __name__ == '__main__':
        t = threading.Thread(target=zmq_thread)
        t.start()
        time.sleep(10)
        isBlocking = False

     

四、C++多线程编程

  1. 多线程的创建

    引用前面的介绍,说明Boost创建线程的四种方式。

    • 最简单的方法

      #include <iostream>
      #include <boost/thread.hpp>
      
      void hello()
      {
      	std::cout << "Hello World"<<std::endl;
      }
      
      int main()
      {
      	boost::thread thrd(&hello);
      	thrd.join();
      	return 0;
      }

       

    • 使用函数对象创建

      #include <iostream>
      #include <boost/thread.hpp>
      
      boost::mutex io_mutex;
      
      class count
      {
      	int id;
      public:
      	count(int id): id(id)
      	{
      	}
      
      	void operator()()
      	{
      		for (int i = 0; i < 10; ++i)
      		{
      			boost::mutex::scoped_lock lock(io_mutex);
      			std::cout << "id" << id << std::endl;
      		}
      	}
      };
      
      int main()
      {
      	boost::thread thrd1(count(1));
      	boost::thread thrd2(count(2));
      	thrd1.join();
      	thrd2.join();
      	return 0;
      }
      

       

    • 使用类的静态成员创建

      #include <iostream>
      #include <boost/thread.hpp>
      
      class HelloWorld
      {
      public:
      	static void Hello()
      	{
      		std::cout << "Hello" << std::endl;
      	}
      	static void Create_Thread()
      	{
      		boost::thread thrd(&Hello);
      		thrd.join();
      	}
      };
      
      int main()
      {
      	HelloWorld hello;
      	hello.Create_Thread();
      	return 0;
      }

       

    • 使用类的普通成员创建

      #include <iostream>
      #include <boost/thread.hpp>
      ​
      class HelloWorld
      {
      public:
          void sayHi()
          {
              std::cout << "Say Hi" << std::endl;
          }
      };
      ​
      int main()
      {
          HelloWorld hello;
          boost::thread thrd(boost::bind(&HelloWorld::sayHi,&hello));
          thrd.join();
          return 0;
      }
      ​
      ​
  2. 线程同步与线程锁

    • 使用独占锁

      1. 独占锁使用boost::mutex作为互斥量,并使用scoped_lock作为独占锁。

      #include <iostream>
      #include <boost/thread.hpp>
      #include <valarray>
      ​
      boost::mutex io_mu;
      int num = 0;
      int MAX_NUM = 10000;
      void func_add()
      {
          for(int i=0;i<MAX_NUM;++i)
          {
              boost::mutex::scoped_lock lock(io_mu);
              num = num + 1;
              std::cout << "num:=" << num << std::endl;
          }
      }
      ​
      int main()
      {
          boost::thread thrd1(&func_add);
          boost::thread thrd2(&func_add);
          thrd1.join();
          thrd2.join();
          return 0;
      }
    • 使用读写锁

      1. 读写锁使用boost::shared_mutex作为互斥量,使用

        boost::unique_lock<boost::shared_mutex> writeLock(mutex);

        作为写锁,使用

        boost::shared_lock<boost::shared_mutex> readLock(mutex);

        作为读锁。

      2. 使用读写锁类似于读写文件,如果有读锁可以存在多个,写锁存在时不能读,同时也不能写。

      3. 读写锁更加复杂,适用于读的次数远远过于写的次数的应用程序。

      #include <iostream>
      #include <boost/thread.hpp>
      #include <valarray>
      
      int num = 0;
      boost::shared_mutex mutex;
      int MAX_SIZE = 10000;
      //boost::mutex mutex;
      void set()
      {
      	for(int i=0;i<MAX_SIZE;++i)
      	{
      		boost::unique_lock<boost::shared_mutex> writeLock(mutex);
      		//boost::mutex::scoped_lock lock(mutex);
      		num += 1;
      		std::cout << "write>>num:=" << num << std::endl;
      	}
      }
      
      void get()
      {
      	for(int i=0;i<MAX_SIZE;++i)
      	{
      		//boost::mutex::scoped_lock lock(mutex);
      		boost::shared_lock<boost::shared_mutex> readLock(mutex);
      		std::cout << "read>>num:=" << num << std::endl;
      	}
      	
      }
      
      int main()
      {
      	boost::thread thrd1(&get);
      	boost::thread thrd2(&set);
      	//boost::thread thrd3(&get);
      	//boost::thread thrd4(&set);
      	thrd1.join();
      	thrd2.join();
      	//thrd3.join();
      	//thrd4.join();
      	return 0;
      }

       

    • 条件变量

      1. 条件变量封装了wait和notifty的操作,用于多线程的“通知”和“等待”,可以用于写消息队列。

      2. 线程安全:多个线程共同执行线程中的方法,不会造成数据不一致性,则认为这个类是线程安全的类。使用线程安全的类一个保证将“锁”操作封装在类的内部。

      3. 线程安全的类要求所有的方法都必须要通过函数获取。

      #include <iostream>
      #include <boost/thread.hpp>
      #include <stack>
      
      int MAX_SIZE = 100;
      int PRODUCER_SIZE = 10000;
      class Buffer
      {
      private:
      	boost::mutex mutex;
      	boost::condition_variable_any cond_put;
      	boost::condition_variable_any cond_get;
      	std::stack<int> m_stack;
      	int m_count, m_capacity;
      public:
      	Buffer(size_t n): m_count(0),m_capacity(n){}
      	void put(int x)
      	{
      		{
      			boost::mutex::scoped_lock lock(mutex);
      			while (is_full())
      			{
      				cond_put.wait(lock);
      			}
      			m_stack.push(x);
      			++m_count;
      		}
      		cond_get.notify_one();
      	}
      	void get(int* x)
      	{
      		{
      			boost::mutex::scoped_lock lock(mutex);
      			while(is_empty())
      			{
      				cond_get.wait(lock);
      			}
      			--m_count;
      			*x = m_stack.top();
      			m_stack.pop();
      		}
      		cond_put.notify_one();
      	}
      
      	bool is_full()
      	{
      		return m_count == m_capacity;
      	}
      	bool is_empty()
      	{
      		return m_count == 0;
      	}
      	int getCount()
      	{
      		return m_count;
      	}
      };
      
      Buffer buf(MAX_SIZE);
      
      void Producer()
      {
      	for(int i=0;i<PRODUCER_SIZE;++i)
      	{
      		buf.put(i);
      	}
      }
      
      void Costomer()
      {
      	for (int i = 0; i<PRODUCER_SIZE/2; ++i)
      	{
      		int data;
      		buf.get(&data);
      	}
      }
      
      int main()
      {
      	boost::thread thrd1(&Producer);
      	boost::thread thrd2(&Costomer);
      	boost::thread thrd3(&Costomer);
      	thrd1.join();
      	thrd2.join();
      	thrd3.join();
      	std::cout << buf.getCount() << std::endl;
      	return 0;
      }

       

    • future

      1. 使用future,可以在主线程中等待线程结束,但是future本身是阻塞的,并且一般在主线程,所以不太适合当做异步IO。

      2. future适合于在主线程中同时开启多个线程(多核CPU)的优势进行复杂运算,然后使用wait_all进行线程的同步,然后再整合多个线程的数据。

      #include <iostream>
      #include <boost/thread.hpp>
      #include <boost/timer.hpp>
      
      int fab(int n)
      {
      	if(n==0||n==1)
      		return 1;
      	return fab(n - 1) + fab(n - 2);
      }
      
      int main(int argc, char* argv[])
      {
      	boost::packaged_task<int> task(boost::bind(fab, 10));
      	boost::unique_future<int> future = task.get_future();
      	boost::thread(boost::move(task));
      	//future.wait();
      	future.timed_wait(boost::posix_time::milliseconds(500));
      	std::cout << future.get() << std::endl;
      	return 0;
      }
      

       

  3. 线程池

    • 在程序中,多线程的创建需要统一的管理,这样才能保证线程能够全部正常结束。普通的方式可以创建线程队列,而更有效率的方式则是通过线程池管理线程。

    • 线程池一般写在全局变量中,在程序退出的时候释放线程池。

      #include <iostream>
      #include <boost/thread.hpp>
      void task(std::string str)
      {
      	std::cout << str << std::endl;
      }
      
      int main(int argc, char* argv[])
      {
      	boost::thread_group threadGroup;
      	threadGroup.create_thread(boost::bind(&task, "zhangsan"));
      	threadGroup.create_thread(boost::bind(&task, "lisi"));
      	threadGroup.create_thread(boost::bind(&task, "wangwu"));
      	threadGroup.join_all();
      	return 0;
      }

       

五、C++多协程编程

  1. asio初步了解

    • 异步定时器模型

      1. 异步定时器中存在两个线程:主线程和定时器线程,因此异步定时器存在线程安全问题。

      #include <boost/asio.hpp>
      #include <iostream>
      using namespace std;
      void handler(boost::system::error_code ec)
      {
          cout << "5s" << endl;
      }
      ​
      int main(int argc, char* argv[])
      {
          boost::asio::io_service io_service;
          boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5));
          timer.async_wait(handler);
          io_service.run();
          return 0;
      }

      异步定时器线程安全问题:

      #include <boost/asio.hpp>
      #include <iostream>
      #include <boost/thread.hpp>
      using namespace std;
      ​
      boost::asio::io_service io_service;
      ​
      void handler(boost::system::error_code ec)
      {
          cout << "Thread Id:= " << this_thread::get_id() << endl;
      }
      ​
      void handler2(boost::system::error_code ec)
      {
          cout << "Thread2 Id:= " << this_thread::get_id() << endl;
      }
      ​
      void run()
      {
          io_service.run();
      }
      ​
      int main(int argc, char* argv[])
      {
          cout << "Main Thread Id:= " << this_thread::get_id() << endl;
          boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5));
          boost::asio::deadline_timer timer2(io_service, boost::posix_time::seconds(2));
          timer.async_wait(handler);
          timer2.async_wait(handler2);
          boost::thread thrd(boost::bind(&run));
          cout << "Child Thread Id:= " << thrd.get_id() << endl;
          //io_service.stop();
          //bool isStop = io_service.stopped();
          //if (isStop)
          //  cout << "线程被正常退出" << endl;
          thrd.join();
          return 0;
      }
       

      执行结果中,主线程,run()线程和定时器回调线程的线程id各不相同。

    • 使用ASIO完成网络编程

  2. Window网络编程模型

    • select选择模型

    • 异步选择模型

    • 事件选择模型

    • 重叠IO模型

    • 完成端口模型

  3. asio详解

    • 同步Socket

    • 异步Socket

  4. libcurl

六、C++多进程编程

  1. zmq

    • ZMQ三种模式

      1. 应答模式

        #include <zmq.h>
        #include <iostream>
        #include <boost/thread.hpp>
        
        void client_thread()
        {
        	zmq_msg_t msg;
        	zmq_msg_init(&msg);
        	void* context = zmq_ctx_new();
        	void* subsriber = zmq_socket(context, ZMQ_REQ);
        	int rc = zmq_connect(subsriber, "tcp://localhost:8888");
        	int times = 5;
        	while(times>0)
        	{
        		char buf[255] = "Hello Client";
        		zmq_send(subsriber, buf, strlen(buf) + 1, 0);
        		int size = zmq_recvmsg(subsriber, &msg, 0);
        		if(size==-1)
        			continue;
        		std::cout << "服务端发送数据: " << (char*)zmq_msg_data(&msg) << std::endl;
        		times--;
        	}
        }
        
        void server_thread()
        {
        	zmq_msg_t msg;
        	zmq_msg_init(&msg);
        	void * context = zmq_ctx_new();
        	void * publisher = zmq_socket(context, ZMQ_REP);
        	int rc = zmq_bind(publisher, "tcp://*:8888");
        	if(rc!=0)
        	{
        		std::cout << "bind error" << std::endl;
        	}
        	int times = 5;
        	while(times>0)
        	{
        		char buf[255];
        		zmq_recv(publisher, buf, 255, 0);
        		std::cout << "服务端接收数据:" << buf << std::endl;
        		char update[65] = "Hello Server";
        		int size = zmq_send(publisher, update, strlen(update)+1, 0);
        		times--;
        	}
        	zmq_close(publisher);
        	zmq_ctx_destroy(context);
        }
        
        
        int main(int argc, char* argv[])
        {
        	boost::thread thrd_client(&client_thread);
        	boost::thread thrd_server(&server_thread);
        	thrd_client.join();
        	thrd_server.join();
        	return 0;
        }

         

      2. 发布/订阅模式

        #include <zmq.h>
        #include <iostream>
        #include <boost/thread.hpp>
        ​
        void client_thread()
        {
            void* context = zmq_ctx_new();
            void* subsriber = zmq_socket(context, ZMQ_SUB);
            int rc = zmq_connect(subsriber, "tcp://localhost:8888");
            rc = zmq_setsockopt(subsriber, ZMQ_SUBSCRIBE, "", 0);
            int times = 5;
            while(times>0)
            {
                char buffer[256];
                int size = zmq_recv(subsriber, buffer, 255, 0);
                if(size==-1)
                    continue;
                buffer[size] = '\0';
                std::cout << buffer << std::endl;
                times--;
            }
        }
        ​
        void server_thread()
        {
            void * context = zmq_ctx_new();
            void * publisher = zmq_socket(context, ZMQ_PUB);
            int rc = zmq_bind(publisher, "tcp://*:8888");
            if(rc!=0)
            {
                std::cout << "bind error" << std::endl;
            }
            int times = 5;
            while(times>0)
            {
                char update[256] = "Hello Server";
                int size = zmq_send(publisher, update, strlen(update)+1, 0);
                std::cout << "发送数据" << std::endl;
                times--;
            }
            
            zmq_close(publisher);
            zmq_ctx_destroy(context);
        }
        ​
        ​
        int main(int argc, char* argv[])
        {
            boost::thread thrd_client(&client_thread);
            boost::thread thrd_server(&server_thread);
            thrd_client.join();
            thrd_server.join();
            return 0;
        }
        ​
        1. 发布订阅模式同样要求接收端必须要在发送端之前先去连接,否则就不会接受到前面几条消息。

        2. 一般情况下,发布订阅模式适合于持续发布消息,接收端上线后就可以接受到,例如消息的推送。

        3. 当发布订阅模式需要进行消息同步时,需要使用一下方式:

          • 发送端先发送假消息,用于同步。

          • 接收端接收到假消息后,给发送端发出应答请求。

          • 发送端接收到应答请求后,开始发送真实的消息。

        4. 可以使用推拉模式,只要让接收端先启动,发送端向接口推送消息,接收端就可以正常收取到。

      3. 推拉模式

        #include <zmq.h>
        #include <iostream>
        #include <boost/thread.hpp>
        ​
        void client_thread()
        {
            void* context = zmq_ctx_new();
            void* subsriber = zmq_socket(context, ZMQ_PULL);
            int rc = zmq_connect(subsriber, "tcp://localhost:8888");
            int times = 5;
            while(times>0)
            {
                char buffer[256];
                int size = zmq_recv(subsriber, buffer, 255, 0);
                if(size==-1)
                    continue;
                buffer[size] = '\0';
                std::cout << buffer << std::endl;
                times--;
            }
        }
        ​
        void server_thread()
        {
            void * context = zmq_ctx_new();
            void * publisher = zmq_socket(context, ZMQ_PUSH);
            int rc = zmq_bind(publisher, "tcp://*:8888");
            if(rc!=0)
            {
                std::cout << "bind error" << std::endl;
            }
            int times = 5;
            while(times>0)
            {
                char update[256] = "Hello Server";
                int size = zmq_send(publisher, update, strlen(update)+1, 0);
                std::cout << "发送数据" << std::endl;
                times--;
            }
            
            zmq_close(publisher);
            zmq_ctx_destroy(context);
        }
        ​
        ​
        int main(int argc, char* argv[])
        {
            boost::thread thrd_client(&client_thread);
            boost::thread thrd_server(&server_thread);
            thrd_client.join();
            thrd_server.join();
            return 0;
        }
        1. 推拉模式中必须要有worker线程的存在,否则推送端就会卡在send函数。

    • ZMQ异步选择模型

      1. 类似于select模型

      #include <zmq.h>
      #include <iostream>
      #include <boost/thread.hpp>
      ​
      void client_thread()
      {
          void* context = zmq_ctx_new();
          void* subsriber = zmq_socket(context, ZMQ_PULL);
          int rc = zmq_connect(subsriber, "tcp://localhost:8888");
          zmq_pollitem_t item[1];
          item[0].socket = subsriber;
          item[0].fd = 0;
          item[0].events = ZMQ_POLLIN;
          while (true)
          {
              std::cout << "try to search" << std::endl;
              int rc = zmq_poll(item, 1, 500);
              if (rc >= 0)
              {
                  if(item[0].revents == ZMQ_POLLIN)
                  {
                      char buffer[256];
                      int size = zmq_recv(subsriber, buffer, 255, 0);
                      if (size == -1)
                          continue;
                      buffer[size] = '\0';
                      std::cout << buffer << std::endl;
                  }
              }
          }
      }
      ​
      void server_thread()
      {
          void * context = zmq_ctx_new();
          void * publisher = zmq_socket(context, ZMQ_PUSH);
          int rc = zmq_bind(publisher, "tcp://*:8888");
          if(rc!=0)
          {
              std::cout << "bind error" << std::endl;
          }
          int times = 5;
          while(times>0)
          {
              char update[256] = "Hello Server";
              int size = zmq_send(publisher, update, strlen(update)+1, 0);
              std::cout << "发送数据" << std::endl;
              times--;
          }
          
          zmq_close(publisher);
          zmq_ctx_destroy(context);
      }
      ​
      ​
      int main(int argc, char* argv[])
      {
          boost::thread thrd_client(&client_thread);
          boost::thread thrd_server(&server_thread);
          thrd_client.join();
          thrd_server.join();
          return 0;
      }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值