网络编程(二)

线程同步与互斥

线程互斥

四种互斥量

  • std::mutex: 最简单的互斥锁
  • std::recursive_mutex:递归mutex类,能多次锁定而不死锁。
  • std::time_mutex:定时mutex类,可以锁定一定的时间。
  • std::recursive_timed_mutex:定时递归mutex类。

两种锁

  • std::lock_guard:方便线程对互斥量上锁。自动加锁和解锁,在构造函数和析构函数中执行。
  • std::unique_lock:方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。

两种锁可以使用上面的四种互斥量进行自动加锁和解锁
区别:

  • unique_lock可以实现延时锁,即先生成unique_lock对象,然后在有需要的地方调用lock函数,lock_guard在对象创建时就自动进行lock操作了;
    • std::adopt_lock: 该互斥量之前必须已经lock,提前加锁,才可以使用该参数。
    • std::try_to_lock: 可以避免一些不必要的等待,会判断当前mutex能否被lock,如果不能被lock,可以先去执行其他代码。这个和adopt不同,不需要自己提前加锁。
    • std::defer_lock: 这个参数表示暂时先不lock,之后手动去lock,但是使用之前也是不允许去lock。一般用来搭配unique_lock的成员函数去使用。
  • unique_lock可以在需要的地方调用unlock操作,而lock_guard只能在其对象生命周期结束后自动Unlock;
    • std::adopt_lock: 表示这个互斥量已经被lock了(你必须要把互斥量提前lock了 ,否者会报异常)
    • std::try_to_lock: 我们会尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,我也会立即返回,并不会阻塞在那里;
    • std::defer_lock: std::defer_lock的意思就是并没有给mutex加锁:初始化了一个没有加锁的mutex。
    • lock(),加锁
    • unlock(),解锁
    • try_lock(): 尝试给互斥量加锁,如果拿不到锁,返回false,如果拿到了锁,返回true,这个函数是不阻塞的
    • release(): 返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不再有关系。严格区分unlock()与release()的区别,不要混淆

相关函数

  • std::try_lock:尝试同时对多个互斥量上锁。
  • std::lock:可以同时对多个互斥量上锁。避免死锁。
  • std::call_once:如果多个线程需要同时调用某个函数,call_once可以保证多个线程对该函数只调用一次。

线程同步

std::condition_variable, 要搭配着mutex来使用,主要的函数为wait和notify函数。
wait函数:
- wait:有两个重载的函数void wait( std::unique_lockstd::mutex& lock )和void wait( std::unique_lockstd::mutex& lock, Predicate stop_waiting ),第二个比第一个加了pred条件,为true的时候才接触阻塞。
- wait_for:增加了一个超时时间
- wait_util: 直到某个时间点
notify函数
- notify_one: 唤醒一个阻塞进程
- notify_all: 唤醒所有阻塞进程

进程间通信

无名管道

无名管道用于有亲缘关系的进程间的通信,因为这样双方才能同时拥有管道的信息。工作方式为半双工。读端和写端固定。
读写的内容存在于内存中。
通过pipe接口创建,返回读端口和写端口:

int pfd[2]={0};//定义无名管道数组,只有两个端口,所以大小为2
int ret=pipe(pfd);//pipe--创建无名管道
write(pfd[1],"hello", 5);
int n = read(pfd[0], buff, 5)

有名管道

有名管道的创建之后会在文件系统中以管道文件的形式存在;
有名管道可以用于任意两个进程间的通信,没有固定的读端和写端。

int mkfifo(const char *pathname, mode_t mode);
int ret=mkfifo("fifo",0755);//0664赋文件权限
//写端打开
int fd=open("fifo",O_WRONLY)
write(fd,buf,strlen(buf));//写入
//读端打开
int fd=open("fifo",O_RDONLY);//以‘仅读’打开管道
read(fd,buf,64);//读取缓冲区

消息队列

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

  • 每个消息的长度受限
  • 队列长队也受限制
  • 读写消息需要于内核之间数据拷贝两次
//用来创建和访问一个消息队列
int msgget(key_t key, int msgflg);
//消息队列的控制函数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//把一条消息添加到消息队列中
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//从一个消息队列接收消息
size_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

key_t key;
key=ftok("./msgfile",'a');
msgid=msgget(key, IPC_CREAT |IPC_EXCL|0666); //通过文件对应
ret = msgsnd(msgid,&msgbuf, sizeof(msgbuf.data), IPC_NOWAIT);
ret=msgrcv(msgid, &msgbuf, sizeof(msgbuf.data), msgtype, IPC_NOWAIT);

信号量

信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。

POSIX信号量


 //信号量存在
sem_t *sem_open(const char *name, int oflag);
//信号量不存在
sem_t *sem_open(const char *name, int oflag,
                       mode_t mode, unsigned int value);
int sem_close(sem_t *sem); //关闭信号量
int sem_unlink(const char *name); //删除信号量文件
sem_wait(sem); // p操作
//v操作+1
sem_post(sem);

共享内存

由于进程通信的本质是要让两个不同的进程看到同一份资源,我们可以在物理内存上开辟一块空间,这块空间被称为共享内存,然后让这两个进程通过某种方式都能访问到这块内存,这样的话,两个进程之间就可以通信了。
共享内存操作默认不阻塞,如果多个进程同时读写共享内存,可能出现数据混乱,共享内存需要借助其他机制来保证进程间的数据同步,比如:信号量,共享内存内部没有提供这种机制。

POSIX共享内存


int shm_open(const char *name, int oflag, mode_t mode);//成功返回非负的描述符,失败返回-1
int shm_unlink(const char *name)//shm_unlink用于删除一个共享内存区对象,跟其他文件的unlink以及其他POSIX IPC的删除操作一样,对象的析构会到对该对象的所有引用全部关闭才会发生。
shm_fd = shm_open("shm-update", O_CREAT|O_RDWR, 0777);
ftruncate(shm_fd, 1024);
shm_data = mmap(NULL,1024, PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, SEEK_SET);

systemV共享内存

key_t ftok(const char *pathname, int proj_id)
//创建或者打开一个共享内存
int shmget(key_t key, size_t size, int shmflg);// shmflg: IPC_CREATE,  IPC_excl
//关联和解除关联函数shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);
//
int shmdt(const void *shmaddr);

你可能感兴趣的:(网络编程,网络)